Skip to content

Commit 38b5acf

Browse files
authored
bpo-46729: add number of sub-exceptions in str() of BaseExceptionGroup (GH-31294)
1 parent bba8008 commit 38b5acf

File tree

4 files changed

+100
-29
lines changed

4 files changed

+100
-29
lines changed

Lib/test/test_exception_group.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,71 @@ class MyBEG(BaseExceptionGroup):
102102
MyBEG)
103103

104104

105+
class StrAndReprTests(unittest.TestCase):
106+
def test_ExceptionGroup(self):
107+
eg = BaseExceptionGroup(
108+
'flat', [ValueError(1), TypeError(2)])
109+
110+
self.assertEqual(str(eg), "flat (2 sub-exceptions)")
111+
self.assertEqual(repr(eg),
112+
"ExceptionGroup('flat', [ValueError(1), TypeError(2)])")
113+
114+
eg = BaseExceptionGroup(
115+
'nested', [eg, ValueError(1), eg, TypeError(2)])
116+
117+
self.assertEqual(str(eg), "nested (4 sub-exceptions)")
118+
self.assertEqual(repr(eg),
119+
"ExceptionGroup('nested', "
120+
"[ExceptionGroup('flat', "
121+
"[ValueError(1), TypeError(2)]), "
122+
"ValueError(1), "
123+
"ExceptionGroup('flat', "
124+
"[ValueError(1), TypeError(2)]), TypeError(2)])")
125+
126+
def test_BaseExceptionGroup(self):
127+
eg = BaseExceptionGroup(
128+
'flat', [ValueError(1), KeyboardInterrupt(2)])
129+
130+
self.assertEqual(str(eg), "flat (2 sub-exceptions)")
131+
self.assertEqual(repr(eg),
132+
"BaseExceptionGroup("
133+
"'flat', "
134+
"[ValueError(1), KeyboardInterrupt(2)])")
135+
136+
eg = BaseExceptionGroup(
137+
'nested', [eg, ValueError(1), eg])
138+
139+
self.assertEqual(str(eg), "nested (3 sub-exceptions)")
140+
self.assertEqual(repr(eg),
141+
"BaseExceptionGroup('nested', "
142+
"[BaseExceptionGroup('flat', "
143+
"[ValueError(1), KeyboardInterrupt(2)]), "
144+
"ValueError(1), "
145+
"BaseExceptionGroup('flat', "
146+
"[ValueError(1), KeyboardInterrupt(2)])])")
147+
148+
def test_custom_exception(self):
149+
class MyEG(ExceptionGroup):
150+
pass
151+
152+
eg = MyEG(
153+
'flat', [ValueError(1), TypeError(2)])
154+
155+
self.assertEqual(str(eg), "flat (2 sub-exceptions)")
156+
self.assertEqual(repr(eg), "MyEG('flat', [ValueError(1), TypeError(2)])")
157+
158+
eg = MyEG(
159+
'nested', [eg, ValueError(1), eg, TypeError(2)])
160+
161+
self.assertEqual(str(eg), "nested (4 sub-exceptions)")
162+
self.assertEqual(repr(eg), (
163+
"MyEG('nested', "
164+
"[MyEG('flat', [ValueError(1), TypeError(2)]), "
165+
"ValueError(1), "
166+
"MyEG('flat', [ValueError(1), TypeError(2)]), "
167+
"TypeError(2)])"))
168+
169+
105170
def create_simple_eg():
106171
excs = []
107172
try:

Lib/test/test_traceback.py

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,7 +1403,7 @@ def exc():
14031403
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
14041404
f' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
14051405
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
1406-
f' | ExceptionGroup: eg\n'
1406+
f' | ExceptionGroup: eg (2 sub-exceptions)\n'
14071407
f' +-+---------------- 1 ----------------\n'
14081408
f' | ValueError: 1\n'
14091409
f' +---------------- 2 ----------------\n'
@@ -1425,7 +1425,7 @@ def exc():
14251425
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n'
14261426
f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n'
14271427
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
1428-
f' | ExceptionGroup: eg1\n'
1428+
f' | ExceptionGroup: eg1 (2 sub-exceptions)\n'
14291429
f' +-+---------------- 1 ----------------\n'
14301430
f' | ValueError: 1\n'
14311431
f' +---------------- 2 ----------------\n'
@@ -1441,7 +1441,7 @@ def exc():
14411441
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
14421442
f' | raise EG("eg2", [ValueError(3), TypeError(4)]) from e\n'
14431443
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
1444-
f' | ExceptionGroup: eg2\n'
1444+
f' | ExceptionGroup: eg2 (2 sub-exceptions)\n'
14451445
f' +-+---------------- 1 ----------------\n'
14461446
f' | ValueError: 3\n'
14471447
f' +---------------- 2 ----------------\n'
@@ -1467,7 +1467,7 @@ def exc():
14671467
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 4}, in exc\n'
14681468
f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n'
14691469
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
1470-
f' | ExceptionGroup: eg1\n'
1470+
f' | ExceptionGroup: eg1 (2 sub-exceptions)\n'
14711471
f' +-+---------------- 1 ----------------\n'
14721472
f' | ValueError: 1\n'
14731473
f' +---------------- 2 ----------------\n'
@@ -1480,7 +1480,7 @@ def exc():
14801480
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
14811481
f' | raise EG("eg2", [ValueError(3), TypeError(4)])\n'
14821482
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
1483-
f' | ExceptionGroup: eg2\n'
1483+
f' | ExceptionGroup: eg2 (2 sub-exceptions)\n'
14841484
f' +-+---------------- 1 ----------------\n'
14851485
f' | ValueError: 3\n'
14861486
f' +---------------- 2 ----------------\n'
@@ -1519,15 +1519,15 @@ def exc():
15191519
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
15201520
f' | raise EG("eg", [VE(1), exc, VE(4)])\n'
15211521
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
1522-
f' | ExceptionGroup: eg\n'
1522+
f' | ExceptionGroup: eg (3 sub-exceptions)\n'
15231523
f' +-+---------------- 1 ----------------\n'
15241524
f' | ValueError: 1\n'
15251525
f' +---------------- 2 ----------------\n'
15261526
f' | Exception Group Traceback (most recent call last):\n'
15271527
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
15281528
f' | raise EG("nested", [TE(2), TE(3)])\n'
15291529
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
1530-
f' | ExceptionGroup: nested\n'
1530+
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
15311531
f' +-+---------------- 1 ----------------\n'
15321532
f' | TypeError: 2\n'
15331533
f' +---------------- 2 ----------------\n'
@@ -1546,7 +1546,7 @@ def exc():
15461546
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 11}, in exc\n'
15471547
f' | raise EG("top", [VE(5)])\n'
15481548
f' | ^^^^^^^^^^^^^^^^^^^^^^^^\n'
1549-
f' | ExceptionGroup: top\n'
1549+
f' | ExceptionGroup: top (1 sub-exception)\n'
15501550
f' +-+---------------- 1 ----------------\n'
15511551
f' | ValueError: 5\n'
15521552
f' +------------------------------------\n')
@@ -1560,7 +1560,7 @@ def test_exception_group_width_limit(self):
15601560
excs.append(ValueError(i))
15611561
eg = ExceptionGroup('eg', excs)
15621562

1563-
expected = (' | ExceptionGroup: eg\n'
1563+
expected = (' | ExceptionGroup: eg (1000 sub-exceptions)\n'
15641564
' +-+---------------- 1 ----------------\n'
15651565
' | ValueError: 0\n'
15661566
' +---------------- 2 ----------------\n'
@@ -1605,43 +1605,43 @@ def test_exception_group_depth_limit(self):
16051605
f'eg{i}',
16061606
[ValueError(i), exc, ValueError(-i)])
16071607

1608-
expected = (' | ExceptionGroup: eg999\n'
1608+
expected = (' | ExceptionGroup: eg999 (3 sub-exceptions)\n'
16091609
' +-+---------------- 1 ----------------\n'
16101610
' | ValueError: 999\n'
16111611
' +---------------- 2 ----------------\n'
1612-
' | ExceptionGroup: eg998\n'
1612+
' | ExceptionGroup: eg998 (3 sub-exceptions)\n'
16131613
' +-+---------------- 1 ----------------\n'
16141614
' | ValueError: 998\n'
16151615
' +---------------- 2 ----------------\n'
1616-
' | ExceptionGroup: eg997\n'
1616+
' | ExceptionGroup: eg997 (3 sub-exceptions)\n'
16171617
' +-+---------------- 1 ----------------\n'
16181618
' | ValueError: 997\n'
16191619
' +---------------- 2 ----------------\n'
1620-
' | ExceptionGroup: eg996\n'
1620+
' | ExceptionGroup: eg996 (3 sub-exceptions)\n'
16211621
' +-+---------------- 1 ----------------\n'
16221622
' | ValueError: 996\n'
16231623
' +---------------- 2 ----------------\n'
1624-
' | ExceptionGroup: eg995\n'
1624+
' | ExceptionGroup: eg995 (3 sub-exceptions)\n'
16251625
' +-+---------------- 1 ----------------\n'
16261626
' | ValueError: 995\n'
16271627
' +---------------- 2 ----------------\n'
1628-
' | ExceptionGroup: eg994\n'
1628+
' | ExceptionGroup: eg994 (3 sub-exceptions)\n'
16291629
' +-+---------------- 1 ----------------\n'
16301630
' | ValueError: 994\n'
16311631
' +---------------- 2 ----------------\n'
1632-
' | ExceptionGroup: eg993\n'
1632+
' | ExceptionGroup: eg993 (3 sub-exceptions)\n'
16331633
' +-+---------------- 1 ----------------\n'
16341634
' | ValueError: 993\n'
16351635
' +---------------- 2 ----------------\n'
1636-
' | ExceptionGroup: eg992\n'
1636+
' | ExceptionGroup: eg992 (3 sub-exceptions)\n'
16371637
' +-+---------------- 1 ----------------\n'
16381638
' | ValueError: 992\n'
16391639
' +---------------- 2 ----------------\n'
1640-
' | ExceptionGroup: eg991\n'
1640+
' | ExceptionGroup: eg991 (3 sub-exceptions)\n'
16411641
' +-+---------------- 1 ----------------\n'
16421642
' | ValueError: 991\n'
16431643
' +---------------- 2 ----------------\n'
1644-
' | ExceptionGroup: eg990\n'
1644+
' | ExceptionGroup: eg990 (3 sub-exceptions)\n'
16451645
' +-+---------------- 1 ----------------\n'
16461646
' | ValueError: 990\n'
16471647
' +---------------- 2 ----------------\n'
@@ -1707,7 +1707,7 @@ def exc():
17071707
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
17081708
f' | raise ExceptionGroup("nested", excs)\n'
17091709
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
1710-
f' | ExceptionGroup: nested\n'
1710+
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
17111711
f' | >> Multi line note\n'
17121712
f' | >> Because I am such\n'
17131713
f' | >> an important exception.\n'
@@ -2460,7 +2460,7 @@ def test_exception_group_construction(self):
24602460
def test_exception_group_format_exception_only(self):
24612461
teg = traceback.TracebackException(*self.eg_info)
24622462
formatted = ''.join(teg.format_exception_only()).split('\n')
2463-
expected = "ExceptionGroup: eg2\n".split('\n')
2463+
expected = "ExceptionGroup: eg2 (2 sub-exceptions)\n".split('\n')
24642464

24652465
self.assertEqual(formatted, expected)
24662466

@@ -2476,13 +2476,13 @@ def test_exception_group_format(self):
24762476
f' | File "{__file__}", line {lno_g+23}, in _get_exception_group',
24772477
f' | raise ExceptionGroup("eg2", [exc3, exc4])',
24782478
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
2479-
f' | ExceptionGroup: eg2',
2479+
f' | ExceptionGroup: eg2 (2 sub-exceptions)',
24802480
f' +-+---------------- 1 ----------------',
24812481
f' | Exception Group Traceback (most recent call last):',
24822482
f' | File "{__file__}", line {lno_g+16}, in _get_exception_group',
24832483
f' | raise ExceptionGroup("eg1", [exc1, exc2])',
24842484
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
2485-
f' | ExceptionGroup: eg1',
2485+
f' | ExceptionGroup: eg1 (2 sub-exceptions)',
24862486
f' +-+---------------- 1 ----------------',
24872487
f' | Traceback (most recent call last):',
24882488
f' | File "{__file__}", line {lno_g+9}, in _get_exception_group',
@@ -2531,9 +2531,9 @@ def test_max_group_width(self):
25312531
formatted = ''.join(teg.format()).split('\n')
25322532

25332533
expected = [
2534-
f' | ExceptionGroup: eg',
2534+
f' | ExceptionGroup: eg (2 sub-exceptions)',
25352535
f' +-+---------------- 1 ----------------',
2536-
f' | ExceptionGroup: eg1',
2536+
f' | ExceptionGroup: eg1 (3 sub-exceptions)',
25372537
f' +-+---------------- 1 ----------------',
25382538
f' | ValueError: 0',
25392539
f' +---------------- 2 ----------------',
@@ -2542,7 +2542,7 @@ def test_max_group_width(self):
25422542
f' | and 1 more exception',
25432543
f' +------------------------------------',
25442544
f' +---------------- 2 ----------------',
2545-
f' | ExceptionGroup: eg2',
2545+
f' | ExceptionGroup: eg2 (10 sub-exceptions)',
25462546
f' +-+---------------- 1 ----------------',
25472547
f' | TypeError: 0',
25482548
f' +---------------- 2 ----------------',
@@ -2563,11 +2563,11 @@ def test_max_group_depth(self):
25632563
formatted = ''.join(teg.format()).split('\n')
25642564

25652565
expected = [
2566-
f' | ExceptionGroup: exc',
2566+
f' | ExceptionGroup: exc (3 sub-exceptions)',
25672567
f' +-+---------------- 1 ----------------',
25682568
f' | ValueError: -2',
25692569
f' +---------------- 2 ----------------',
2570-
f' | ExceptionGroup: exc',
2570+
f' | ExceptionGroup: exc (3 sub-exceptions)',
25712571
f' +-+---------------- 1 ----------------',
25722572
f' | ValueError: -1',
25732573
f' +---------------- 2 ----------------',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add number of sub-exceptions to :meth:`BaseException.__str__`.

Objects/exceptions.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,12 @@ BaseExceptionGroup_str(PyBaseExceptionGroupObject *self)
836836
{
837837
assert(self->msg);
838838
assert(PyUnicode_Check(self->msg));
839-
return Py_NewRef(self->msg);
839+
840+
assert(PyTuple_CheckExact(self->excs));
841+
Py_ssize_t num_excs = PyTuple_Size(self->excs);
842+
return PyUnicode_FromFormat(
843+
"%S (%zd sub-exception%s)",
844+
self->msg, num_excs, num_excs > 1 ? "s" : "");
840845
}
841846

842847
static PyObject *

0 commit comments

Comments
 (0)