@@ -31,10 +31,10 @@ Currently there is no standard way to obtain this information. Python authors
3131must manually add timing to exception messages or rely on logging frameworks,
3232which can be costly and is inconsistently done and error-prone.
3333
34- Consider an async service that fetches data from multiple backends concurrently
35- using `` asyncio.TaskGroup ``. When several backends fail, the resulting
36- ``ExceptionGroup `` contains all the errors but no indication of their temporal
37- ordering ::
34+ Consider an async service that fetches data from multiple backends
35+ concurrently and reports every failure rather than failing fast. The
36+ resulting ``ExceptionGroup `` contains all the errors in submission order,
37+ with no indication of when each one occurred ::
3838
3939 import asyncio
4040
@@ -50,52 +50,53 @@ ordering::
5050 await asyncio.sleep(2.3)
5151 raise TimeoutError("Recommendation service timeout")
5252
53- async def fetch_inventory(items):
54- await asyncio.sleep(0.8)
55- raise KeyError("Item 'widget-42' not found in inventory")
56-
5753 async def get_dashboard(uid):
58- async with asyncio.TaskGroup() as tg:
59- tg.create_task(fetch_user(uid))
60- tg.create_task(fetch_orders(uid))
61- tg.create_task(fetch_recommendations(uid))
62- tg.create_task(fetch_inventory(['widget-42']))
54+ results = await asyncio.gather(
55+ fetch_user(uid),
56+ fetch_orders(uid),
57+ fetch_recommendations(uid),
58+ return_exceptions=True,
59+ )
60+ errors = [r for r in results if isinstance(r, Exception)]
61+ if errors:
62+ raise ExceptionGroup("dashboard fetch failed", errors)
6363
6464 asyncio.run(get_dashboard("usr_12@34"))
6565
6666With ``PYTHON_TRACEBACK_TIMESTAMPS=iso ``, the output becomes:
6767
6868.. code-block :: text
6969
70- Traceback (most recent call last):
71- ...
72- ExceptionGroup: unhandled errors in a TaskGroup (4 sub-exceptions)
70+ + Exception Group Traceback (most recent call last):
71+ | File "service.py", line 26, in <module>
72+ | asyncio.run(get_dashboard("usr_12@34"))
73+ | ...
74+ | File "service.py", line 24, in get_dashboard
75+ | raise ExceptionGroup("dashboard fetch failed", errors)
76+ | ExceptionGroup: dashboard fetch failed (3 sub-exceptions) <@2026-04-19T07:24:31.102431Z>
7377 +-+---------------- 1 ----------------
7478 | Traceback (most recent call last):
75- | File "service.py", line 11 , in fetch_orders
76- | raise ValueError (f"Invalid user_id format: {uid}")
77- | ValueError: Invalid user_id format: usr_12@34 <@2025-03-15T10:23:41.142857Z >
79+ | File "service.py", line 5 , in fetch_user
80+ | raise ConnectionError (f"User service timeout for {uid}")
81+ | ConnectionError: User service timeout for usr_12@34 <@2026-04-19T07:24:29.300461Z >
7882 +---------------- 2 ----------------
7983 | Traceback (most recent call last):
80- | File "service.py", line 7 , in fetch_user
81- | raise ConnectionError (f"User service timeout for {uid}")
82- | ConnectionError: User service timeout for usr_12@34 <@2025-03-15T10:23:41.542901Z >
84+ | File "service.py", line 9 , in fetch_orders
85+ | raise ValueError (f"Invalid user_id format: {uid}")
86+ | ValueError: Invalid user_id format: usr_12@34 <@2026-04-19T07:24:28.899918Z >
8387 +---------------- 3 ----------------
8488 | Traceback (most recent call last):
85- | File "service.py", line 19, in fetch_inventory
86- | raise KeyError("Item 'widget-42' not found in inventory")
87- | KeyError: "Item 'widget-42' not found in inventory" <@2025-03-15T10:23:41.842856Z>
88- +---------------- 4 ----------------
89- | Traceback (most recent call last):
90- | File "service.py", line 15, in fetch_recommendations
89+ | File "service.py", line 13, in fetch_recommendations
9190 | raise TimeoutError("Recommendation service timeout")
92- | TimeoutError: Recommendation service timeout <@2025-03-15T10:23:43.342912Z>
91+ | TimeoutError: Recommendation service timeout <@2026-04-19T07:24:31.102394Z>
92+ +------------------------------------
9393
94- The timestamps immediately reveal that the order validation failed first
95- (at .142s), while the recommendation service was the slowest at 2.3 seconds.
96- That could also be correlated with metrics dashboards, load balancer logs, or
97- traces from other services or even logs from the program itself to build a
98- complete picture.
94+ The sub-exceptions are listed in submission order, but the timestamps reveal
95+ that the order validation actually failed first (at 28.899s), the user
96+ service half a second later, and the recommendation service last after 2.3
97+ seconds. These can also be correlated with metrics dashboards, load
98+ balancer logs, traces from other services, or the program's own logs to
99+ build a complete picture.
99100
100101
101102Specification
@@ -401,7 +402,7 @@ Using ``sys.excepthook``
401402
402403``sys.excepthook `` runs only when an uncaught exception reaches the top
403404level, at display time rather than when the exception was created. For the
404- motivating `` TaskGroup `` example, the hook would fire once for the resulting
405+ motivating example above , the hook would fire once for the resulting
405406``ExceptionGroup `` after all tasks have completed, so every sub-exception
406407would receive the same timestamp. Exceptions that are caught and logged
407408never reach the hook at all.
@@ -622,7 +623,7 @@ Change History
622623 coarse-resolution clock; reworded the Runtime API rejection.
623624 - Added an Open Issues section covering display location, benchmarking the
624625 ``sys.monitoring `` alternative, and recording time of first raise.
625- - Made the Motivation example self-contained.
626+ - Reworked the Motivation example to be self-contained.
626627
627628
628629Copyright
0 commit comments