bpo-46729: add number of sub-exceptions in str() of BaseExceptionGroup (GH-31294)

This commit is contained in:
Irit Katriel 2022-02-22 18:28:58 +00:00 committed by GitHub
parent bba8008f99
commit 38b5acf867
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 29 deletions

View File

@ -102,6 +102,71 @@ class InstanceCreation(unittest.TestCase):
MyBEG) MyBEG)
class StrAndReprTests(unittest.TestCase):
def test_ExceptionGroup(self):
eg = BaseExceptionGroup(
'flat', [ValueError(1), TypeError(2)])
self.assertEqual(str(eg), "flat (2 sub-exceptions)")
self.assertEqual(repr(eg),
"ExceptionGroup('flat', [ValueError(1), TypeError(2)])")
eg = BaseExceptionGroup(
'nested', [eg, ValueError(1), eg, TypeError(2)])
self.assertEqual(str(eg), "nested (4 sub-exceptions)")
self.assertEqual(repr(eg),
"ExceptionGroup('nested', "
"[ExceptionGroup('flat', "
"[ValueError(1), TypeError(2)]), "
"ValueError(1), "
"ExceptionGroup('flat', "
"[ValueError(1), TypeError(2)]), TypeError(2)])")
def test_BaseExceptionGroup(self):
eg = BaseExceptionGroup(
'flat', [ValueError(1), KeyboardInterrupt(2)])
self.assertEqual(str(eg), "flat (2 sub-exceptions)")
self.assertEqual(repr(eg),
"BaseExceptionGroup("
"'flat', "
"[ValueError(1), KeyboardInterrupt(2)])")
eg = BaseExceptionGroup(
'nested', [eg, ValueError(1), eg])
self.assertEqual(str(eg), "nested (3 sub-exceptions)")
self.assertEqual(repr(eg),
"BaseExceptionGroup('nested', "
"[BaseExceptionGroup('flat', "
"[ValueError(1), KeyboardInterrupt(2)]), "
"ValueError(1), "
"BaseExceptionGroup('flat', "
"[ValueError(1), KeyboardInterrupt(2)])])")
def test_custom_exception(self):
class MyEG(ExceptionGroup):
pass
eg = MyEG(
'flat', [ValueError(1), TypeError(2)])
self.assertEqual(str(eg), "flat (2 sub-exceptions)")
self.assertEqual(repr(eg), "MyEG('flat', [ValueError(1), TypeError(2)])")
eg = MyEG(
'nested', [eg, ValueError(1), eg, TypeError(2)])
self.assertEqual(str(eg), "nested (4 sub-exceptions)")
self.assertEqual(repr(eg), (
"MyEG('nested', "
"[MyEG('flat', [ValueError(1), TypeError(2)]), "
"ValueError(1), "
"MyEG('flat', [ValueError(1), TypeError(2)]), "
"TypeError(2)])"))
def create_simple_eg(): def create_simple_eg():
excs = [] excs = []
try: try:

View File

@ -1403,7 +1403,7 @@ class BaseExceptionReportingTests:
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
f' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n' f' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg\n' f' | ExceptionGroup: eg (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n' f' +-+---------------- 1 ----------------\n'
f' | ValueError: 1\n' f' | ValueError: 1\n'
f' +---------------- 2 ----------------\n' f' +---------------- 2 ----------------\n'
@ -1425,7 +1425,7 @@ class BaseExceptionReportingTests:
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n'
f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n' f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n'
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg1\n' f' | ExceptionGroup: eg1 (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n' f' +-+---------------- 1 ----------------\n'
f' | ValueError: 1\n' f' | ValueError: 1\n'
f' +---------------- 2 ----------------\n' f' +---------------- 2 ----------------\n'
@ -1441,7 +1441,7 @@ class BaseExceptionReportingTests:
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
f' | raise EG("eg2", [ValueError(3), TypeError(4)]) from e\n' f' | raise EG("eg2", [ValueError(3), TypeError(4)]) from e\n'
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg2\n' f' | ExceptionGroup: eg2 (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n' f' +-+---------------- 1 ----------------\n'
f' | ValueError: 3\n' f' | ValueError: 3\n'
f' +---------------- 2 ----------------\n' f' +---------------- 2 ----------------\n'
@ -1467,7 +1467,7 @@ class BaseExceptionReportingTests:
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 4}, in exc\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 4}, in exc\n'
f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n' f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n'
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg1\n' f' | ExceptionGroup: eg1 (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n' f' +-+---------------- 1 ----------------\n'
f' | ValueError: 1\n' f' | ValueError: 1\n'
f' +---------------- 2 ----------------\n' f' +---------------- 2 ----------------\n'
@ -1480,7 +1480,7 @@ class BaseExceptionReportingTests:
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
f' | raise EG("eg2", [ValueError(3), TypeError(4)])\n' f' | raise EG("eg2", [ValueError(3), TypeError(4)])\n'
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg2\n' f' | ExceptionGroup: eg2 (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n' f' +-+---------------- 1 ----------------\n'
f' | ValueError: 3\n' f' | ValueError: 3\n'
f' +---------------- 2 ----------------\n' f' +---------------- 2 ----------------\n'
@ -1519,7 +1519,7 @@ class BaseExceptionReportingTests:
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
f' | raise EG("eg", [VE(1), exc, VE(4)])\n' f' | raise EG("eg", [VE(1), exc, VE(4)])\n'
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg\n' f' | ExceptionGroup: eg (3 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n' f' +-+---------------- 1 ----------------\n'
f' | ValueError: 1\n' f' | ValueError: 1\n'
f' +---------------- 2 ----------------\n' f' +---------------- 2 ----------------\n'
@ -1527,7 +1527,7 @@ class BaseExceptionReportingTests:
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
f' | raise EG("nested", [TE(2), TE(3)])\n' f' | raise EG("nested", [TE(2), TE(3)])\n'
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: nested\n' f' | ExceptionGroup: nested (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n' f' +-+---------------- 1 ----------------\n'
f' | TypeError: 2\n' f' | TypeError: 2\n'
f' +---------------- 2 ----------------\n' f' +---------------- 2 ----------------\n'
@ -1546,7 +1546,7 @@ class BaseExceptionReportingTests:
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 11}, in exc\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 11}, in exc\n'
f' | raise EG("top", [VE(5)])\n' f' | raise EG("top", [VE(5)])\n'
f' | ^^^^^^^^^^^^^^^^^^^^^^^^\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: top\n' f' | ExceptionGroup: top (1 sub-exception)\n'
f' +-+---------------- 1 ----------------\n' f' +-+---------------- 1 ----------------\n'
f' | ValueError: 5\n' f' | ValueError: 5\n'
f' +------------------------------------\n') f' +------------------------------------\n')
@ -1560,7 +1560,7 @@ class BaseExceptionReportingTests:
excs.append(ValueError(i)) excs.append(ValueError(i))
eg = ExceptionGroup('eg', excs) eg = ExceptionGroup('eg', excs)
expected = (' | ExceptionGroup: eg\n' expected = (' | ExceptionGroup: eg (1000 sub-exceptions)\n'
' +-+---------------- 1 ----------------\n' ' +-+---------------- 1 ----------------\n'
' | ValueError: 0\n' ' | ValueError: 0\n'
' +---------------- 2 ----------------\n' ' +---------------- 2 ----------------\n'
@ -1605,43 +1605,43 @@ class BaseExceptionReportingTests:
f'eg{i}', f'eg{i}',
[ValueError(i), exc, ValueError(-i)]) [ValueError(i), exc, ValueError(-i)])
expected = (' | ExceptionGroup: eg999\n' expected = (' | ExceptionGroup: eg999 (3 sub-exceptions)\n'
' +-+---------------- 1 ----------------\n' ' +-+---------------- 1 ----------------\n'
' | ValueError: 999\n' ' | ValueError: 999\n'
' +---------------- 2 ----------------\n' ' +---------------- 2 ----------------\n'
' | ExceptionGroup: eg998\n' ' | ExceptionGroup: eg998 (3 sub-exceptions)\n'
' +-+---------------- 1 ----------------\n' ' +-+---------------- 1 ----------------\n'
' | ValueError: 998\n' ' | ValueError: 998\n'
' +---------------- 2 ----------------\n' ' +---------------- 2 ----------------\n'
' | ExceptionGroup: eg997\n' ' | ExceptionGroup: eg997 (3 sub-exceptions)\n'
' +-+---------------- 1 ----------------\n' ' +-+---------------- 1 ----------------\n'
' | ValueError: 997\n' ' | ValueError: 997\n'
' +---------------- 2 ----------------\n' ' +---------------- 2 ----------------\n'
' | ExceptionGroup: eg996\n' ' | ExceptionGroup: eg996 (3 sub-exceptions)\n'
' +-+---------------- 1 ----------------\n' ' +-+---------------- 1 ----------------\n'
' | ValueError: 996\n' ' | ValueError: 996\n'
' +---------------- 2 ----------------\n' ' +---------------- 2 ----------------\n'
' | ExceptionGroup: eg995\n' ' | ExceptionGroup: eg995 (3 sub-exceptions)\n'
' +-+---------------- 1 ----------------\n' ' +-+---------------- 1 ----------------\n'
' | ValueError: 995\n' ' | ValueError: 995\n'
' +---------------- 2 ----------------\n' ' +---------------- 2 ----------------\n'
' | ExceptionGroup: eg994\n' ' | ExceptionGroup: eg994 (3 sub-exceptions)\n'
' +-+---------------- 1 ----------------\n' ' +-+---------------- 1 ----------------\n'
' | ValueError: 994\n' ' | ValueError: 994\n'
' +---------------- 2 ----------------\n' ' +---------------- 2 ----------------\n'
' | ExceptionGroup: eg993\n' ' | ExceptionGroup: eg993 (3 sub-exceptions)\n'
' +-+---------------- 1 ----------------\n' ' +-+---------------- 1 ----------------\n'
' | ValueError: 993\n' ' | ValueError: 993\n'
' +---------------- 2 ----------------\n' ' +---------------- 2 ----------------\n'
' | ExceptionGroup: eg992\n' ' | ExceptionGroup: eg992 (3 sub-exceptions)\n'
' +-+---------------- 1 ----------------\n' ' +-+---------------- 1 ----------------\n'
' | ValueError: 992\n' ' | ValueError: 992\n'
' +---------------- 2 ----------------\n' ' +---------------- 2 ----------------\n'
' | ExceptionGroup: eg991\n' ' | ExceptionGroup: eg991 (3 sub-exceptions)\n'
' +-+---------------- 1 ----------------\n' ' +-+---------------- 1 ----------------\n'
' | ValueError: 991\n' ' | ValueError: 991\n'
' +---------------- 2 ----------------\n' ' +---------------- 2 ----------------\n'
' | ExceptionGroup: eg990\n' ' | ExceptionGroup: eg990 (3 sub-exceptions)\n'
' +-+---------------- 1 ----------------\n' ' +-+---------------- 1 ----------------\n'
' | ValueError: 990\n' ' | ValueError: 990\n'
' +---------------- 2 ----------------\n' ' +---------------- 2 ----------------\n'
@ -1707,7 +1707,7 @@ class BaseExceptionReportingTests:
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
f' | raise ExceptionGroup("nested", excs)\n' f' | raise ExceptionGroup("nested", excs)\n'
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: nested\n' f' | ExceptionGroup: nested (2 sub-exceptions)\n'
f' | >> Multi line note\n' f' | >> Multi line note\n'
f' | >> Because I am such\n' f' | >> Because I am such\n'
f' | >> an important exception.\n' f' | >> an important exception.\n'
@ -2460,7 +2460,7 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
def test_exception_group_format_exception_only(self): def test_exception_group_format_exception_only(self):
teg = traceback.TracebackException(*self.eg_info) teg = traceback.TracebackException(*self.eg_info)
formatted = ''.join(teg.format_exception_only()).split('\n') formatted = ''.join(teg.format_exception_only()).split('\n')
expected = "ExceptionGroup: eg2\n".split('\n') expected = "ExceptionGroup: eg2 (2 sub-exceptions)\n".split('\n')
self.assertEqual(formatted, expected) self.assertEqual(formatted, expected)
@ -2476,13 +2476,13 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
f' | File "{__file__}", line {lno_g+23}, in _get_exception_group', f' | File "{__file__}", line {lno_g+23}, in _get_exception_group',
f' | raise ExceptionGroup("eg2", [exc3, exc4])', f' | raise ExceptionGroup("eg2", [exc3, exc4])',
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^', f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
f' | ExceptionGroup: eg2', f' | ExceptionGroup: eg2 (2 sub-exceptions)',
f' +-+---------------- 1 ----------------', f' +-+---------------- 1 ----------------',
f' | Exception Group Traceback (most recent call last):', f' | Exception Group Traceback (most recent call last):',
f' | File "{__file__}", line {lno_g+16}, in _get_exception_group', f' | File "{__file__}", line {lno_g+16}, in _get_exception_group',
f' | raise ExceptionGroup("eg1", [exc1, exc2])', f' | raise ExceptionGroup("eg1", [exc1, exc2])',
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^', f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
f' | ExceptionGroup: eg1', f' | ExceptionGroup: eg1 (2 sub-exceptions)',
f' +-+---------------- 1 ----------------', f' +-+---------------- 1 ----------------',
f' | Traceback (most recent call last):', f' | Traceback (most recent call last):',
f' | File "{__file__}", line {lno_g+9}, in _get_exception_group', f' | File "{__file__}", line {lno_g+9}, in _get_exception_group',
@ -2531,9 +2531,9 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
formatted = ''.join(teg.format()).split('\n') formatted = ''.join(teg.format()).split('\n')
expected = [ expected = [
f' | ExceptionGroup: eg', f' | ExceptionGroup: eg (2 sub-exceptions)',
f' +-+---------------- 1 ----------------', f' +-+---------------- 1 ----------------',
f' | ExceptionGroup: eg1', f' | ExceptionGroup: eg1 (3 sub-exceptions)',
f' +-+---------------- 1 ----------------', f' +-+---------------- 1 ----------------',
f' | ValueError: 0', f' | ValueError: 0',
f' +---------------- 2 ----------------', f' +---------------- 2 ----------------',
@ -2542,7 +2542,7 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
f' | and 1 more exception', f' | and 1 more exception',
f' +------------------------------------', f' +------------------------------------',
f' +---------------- 2 ----------------', f' +---------------- 2 ----------------',
f' | ExceptionGroup: eg2', f' | ExceptionGroup: eg2 (10 sub-exceptions)',
f' +-+---------------- 1 ----------------', f' +-+---------------- 1 ----------------',
f' | TypeError: 0', f' | TypeError: 0',
f' +---------------- 2 ----------------', f' +---------------- 2 ----------------',
@ -2563,11 +2563,11 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
formatted = ''.join(teg.format()).split('\n') formatted = ''.join(teg.format()).split('\n')
expected = [ expected = [
f' | ExceptionGroup: exc', f' | ExceptionGroup: exc (3 sub-exceptions)',
f' +-+---------------- 1 ----------------', f' +-+---------------- 1 ----------------',
f' | ValueError: -2', f' | ValueError: -2',
f' +---------------- 2 ----------------', f' +---------------- 2 ----------------',
f' | ExceptionGroup: exc', f' | ExceptionGroup: exc (3 sub-exceptions)',
f' +-+---------------- 1 ----------------', f' +-+---------------- 1 ----------------',
f' | ValueError: -1', f' | ValueError: -1',
f' +---------------- 2 ----------------', f' +---------------- 2 ----------------',

View File

@ -0,0 +1 @@
Add number of sub-exceptions to :meth:`BaseException.__str__`.

View File

@ -836,7 +836,12 @@ BaseExceptionGroup_str(PyBaseExceptionGroupObject *self)
{ {
assert(self->msg); assert(self->msg);
assert(PyUnicode_Check(self->msg)); assert(PyUnicode_Check(self->msg));
return Py_NewRef(self->msg);
assert(PyTuple_CheckExact(self->excs));
Py_ssize_t num_excs = PyTuple_Size(self->excs);
return PyUnicode_FromFormat(
"%S (%zd sub-exception%s)",
self->msg, num_excs, num_excs > 1 ? "s" : "");
} }
static PyObject * static PyObject *