From 7c56a644a0b35f1d2443e4db8ff96ad89bef91f2 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Sat, 12 Feb 2022 11:02:18 +0000 Subject: [PATCH 1/4] bpo-46729: improved str() for BaseExceptionGroup --- Include/cpython/pyerrors.h | 1 + Lib/test/test_exception_group.py | 65 ++++++++++++++++++++++++++++++++ Lib/test/test_traceback.py | 56 +++++++++++++-------------- Objects/exceptions.c | 13 ++++++- 4 files changed, 106 insertions(+), 29 deletions(-) diff --git a/Include/cpython/pyerrors.h b/Include/cpython/pyerrors.h index 5281fde1f1a54c..a421523487243f 100644 --- a/Include/cpython/pyerrors.h +++ b/Include/cpython/pyerrors.h @@ -18,6 +18,7 @@ typedef struct { PyException_HEAD PyObject *msg; PyObject *excs; + Py_ssize_t leaf_count; } PyBaseExceptionGroupObject; typedef struct { diff --git a/Lib/test/test_exception_group.py b/Lib/test/test_exception_group.py index b7b53bb2f0ecef..7f2ef64cbbf53e 100644 --- a/Lib/test/test_exception_group.py +++ b/Lib/test/test_exception_group.py @@ -102,6 +102,71 @@ class MyBEG(BaseExceptionGroup): MyBEG) +class StrAndReprTests(unittest.TestCase): + def test_ExceptionGroup(self): + eg = BaseExceptionGroup( + 'flat', [ValueError(1), TypeError(2)]) + + self.assertEqual(str(eg), "flat (group of 2 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 (group of 6 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 (group of 2 exceptions)") + self.assertEqual(repr(eg), + "BaseExceptionGroup(" + "'flat', " + "[ValueError(1), KeyboardInterrupt(2)])") + + eg = BaseExceptionGroup( + 'nested', [eg, ValueError(1), eg]) + + self.assertEqual(str(eg), "nested (group of 5 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 (group of 2 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 (group of 6 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(): excs = [] try: diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 0aa76aeb4ce093..36ffeecc023e9c 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -1403,7 +1403,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n' f' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: eg\n' + f' | ExceptionGroup: eg (group of 2 exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 1\n' f' +---------------- 2 ----------------\n' @@ -1425,7 +1425,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n' f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: eg1\n' + f' | ExceptionGroup: eg1 (group of 2 exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 1\n' f' +---------------- 2 ----------------\n' @@ -1441,7 +1441,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n' f' | raise EG("eg2", [ValueError(3), TypeError(4)]) from e\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: eg2\n' + f' | ExceptionGroup: eg2 (group of 2 exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 3\n' f' +---------------- 2 ----------------\n' @@ -1467,7 +1467,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 4}, in exc\n' f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: eg1\n' + f' | ExceptionGroup: eg1 (group of 2 exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 1\n' f' +---------------- 2 ----------------\n' @@ -1480,7 +1480,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n' f' | raise EG("eg2", [ValueError(3), TypeError(4)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: eg2\n' + f' | ExceptionGroup: eg2 (group of 2 exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 3\n' f' +---------------- 2 ----------------\n' @@ -1519,7 +1519,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n' f' | raise EG("eg", [VE(1), exc, VE(4)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: eg\n' + f' | ExceptionGroup: eg (group of 4 exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 1\n' f' +---------------- 2 ----------------\n' @@ -1527,7 +1527,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n' f' | raise EG("nested", [TE(2), TE(3)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: nested\n' + f' | ExceptionGroup: nested (group of 2 exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | TypeError: 2\n' f' +---------------- 2 ----------------\n' @@ -1546,7 +1546,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 11}, in exc\n' f' | raise EG("top", [VE(5)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: top\n' + f' | ExceptionGroup: top (group of 1 exception)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 5\n' f' +------------------------------------\n') @@ -1560,7 +1560,7 @@ def test_exception_group_width_limit(self): excs.append(ValueError(i)) eg = ExceptionGroup('eg', excs) - expected = (' | ExceptionGroup: eg\n' + expected = (' | ExceptionGroup: eg (group of 1000 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 0\n' ' +---------------- 2 ----------------\n' @@ -1605,43 +1605,43 @@ def test_exception_group_depth_limit(self): f'eg{i}', [ValueError(i), exc, ValueError(-i)]) - expected = (' | ExceptionGroup: eg999\n' + expected = (' | ExceptionGroup: eg999 (group of 2001 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 999\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg998\n' + ' | ExceptionGroup: eg998 (group of 1999 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 998\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg997\n' + ' | ExceptionGroup: eg997 (group of 1997 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 997\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg996\n' + ' | ExceptionGroup: eg996 (group of 1995 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 996\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg995\n' + ' | ExceptionGroup: eg995 (group of 1993 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 995\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg994\n' + ' | ExceptionGroup: eg994 (group of 1991 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 994\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg993\n' + ' | ExceptionGroup: eg993 (group of 1989 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 993\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg992\n' + ' | ExceptionGroup: eg992 (group of 1987 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 992\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg991\n' + ' | ExceptionGroup: eg991 (group of 1985 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 991\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg990\n' + ' | ExceptionGroup: eg990 (group of 1983 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 990\n' ' +---------------- 2 ----------------\n' @@ -1707,7 +1707,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n' f' | raise ExceptionGroup("nested", excs)\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: nested\n' + f' | ExceptionGroup: nested (group of 2 exceptions)\n' f' | >> Multi line note\n' f' | >> Because I am such\n' f' | >> an important exception.\n' @@ -2460,7 +2460,7 @@ def test_exception_group_construction(self): def test_exception_group_format_exception_only(self): teg = traceback.TracebackException(*self.eg_info) formatted = ''.join(teg.format_exception_only()).split('\n') - expected = "ExceptionGroup: eg2\n".split('\n') + expected = "ExceptionGroup: eg2 (group of 3 exceptions)\n".split('\n') self.assertEqual(formatted, expected) @@ -2476,13 +2476,13 @@ def test_exception_group_format(self): f' | File "{__file__}", line {lno_g+23}, in _get_exception_group', f' | raise ExceptionGroup("eg2", [exc3, exc4])', f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^', - f' | ExceptionGroup: eg2', + f' | ExceptionGroup: eg2 (group of 3 exceptions)', f' +-+---------------- 1 ----------------', f' | Exception Group Traceback (most recent call last):', f' | File "{__file__}", line {lno_g+16}, in _get_exception_group', f' | raise ExceptionGroup("eg1", [exc1, exc2])', f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^', - f' | ExceptionGroup: eg1', + f' | ExceptionGroup: eg1 (group of 2 exceptions)', f' +-+---------------- 1 ----------------', f' | Traceback (most recent call last):', f' | File "{__file__}", line {lno_g+9}, in _get_exception_group', @@ -2531,9 +2531,9 @@ def test_max_group_width(self): formatted = ''.join(teg.format()).split('\n') expected = [ - f' | ExceptionGroup: eg', + f' | ExceptionGroup: eg (group of 13 exceptions)', f' +-+---------------- 1 ----------------', - f' | ExceptionGroup: eg1', + f' | ExceptionGroup: eg1 (group of 3 exceptions)', f' +-+---------------- 1 ----------------', f' | ValueError: 0', f' +---------------- 2 ----------------', @@ -2542,7 +2542,7 @@ def test_max_group_width(self): f' | and 1 more exception', f' +------------------------------------', f' +---------------- 2 ----------------', - f' | ExceptionGroup: eg2', + f' | ExceptionGroup: eg2 (group of 10 exceptions)', f' +-+---------------- 1 ----------------', f' | TypeError: 0', f' +---------------- 2 ----------------', @@ -2563,11 +2563,11 @@ def test_max_group_depth(self): formatted = ''.join(teg.format()).split('\n') expected = [ - f' | ExceptionGroup: exc', + f' | ExceptionGroup: exc (group of 7 exceptions)', f' +-+---------------- 1 ----------------', f' | ValueError: -2', f' +---------------- 2 ----------------', - f' | ExceptionGroup: exc', + f' | ExceptionGroup: exc (group of 5 exceptions)', f' +-+---------------- 1 ----------------', f' | ValueError: -1', f' +---------------- 2 ----------------', diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 9b1c9f1018077b..fb554f74b7e699 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -714,6 +714,7 @@ BaseExceptionGroup_new(PyTypeObject *type, PyObject *args, PyObject *kwds) goto error; } + Py_ssize_t leaf_count = 0; bool nested_base_exceptions = false; for (Py_ssize_t i = 0; i < numexcs; i++) { PyObject *exc = PyTuple_GET_ITEM(exceptions, i); @@ -734,6 +735,12 @@ BaseExceptionGroup_new(PyTypeObject *type, PyObject *args, PyObject *kwds) else if (is_nonbase_exception == 0) { nested_base_exceptions = true; } + if (_PyBaseExceptionGroup_Check(exc)) { + leaf_count += ((PyBaseExceptionGroupObject *)exc)->leaf_count; + } + else { + leaf_count++; + } } PyTypeObject *cls = type; @@ -770,6 +777,7 @@ BaseExceptionGroup_new(PyTypeObject *type, PyObject *args, PyObject *kwds) self->msg = Py_NewRef(message); self->excs = exceptions; + self->leaf_count = leaf_count; return (PyObject*)self; error: Py_DECREF(exceptions); @@ -836,7 +844,10 @@ BaseExceptionGroup_str(PyBaseExceptionGroupObject *self) { assert(self->msg); assert(PyUnicode_Check(self->msg)); - return Py_NewRef(self->msg); + + return PyUnicode_FromFormat( + "%S (group of %zd exception%s)", + self->msg, self->leaf_count, self->leaf_count > 1 ? "s" : ""); } static PyObject * From 0dbb8186149e56e0de107c1d124af9e7b569481b Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Fri, 18 Feb 2022 22:09:06 +0000 Subject: [PATCH 2/4] report only number of directly nested exceptions --- Include/cpython/pyerrors.h | 1 - Lib/test/test_exception_group.py | 6 +++--- Lib/test/test_traceback.py | 32 ++++++++++++++++---------------- Objects/exceptions.c | 12 +++--------- 4 files changed, 22 insertions(+), 29 deletions(-) diff --git a/Include/cpython/pyerrors.h b/Include/cpython/pyerrors.h index a421523487243f..5281fde1f1a54c 100644 --- a/Include/cpython/pyerrors.h +++ b/Include/cpython/pyerrors.h @@ -18,7 +18,6 @@ typedef struct { PyException_HEAD PyObject *msg; PyObject *excs; - Py_ssize_t leaf_count; } PyBaseExceptionGroupObject; typedef struct { diff --git a/Lib/test/test_exception_group.py b/Lib/test/test_exception_group.py index 7f2ef64cbbf53e..ccdb3e29fd8088 100644 --- a/Lib/test/test_exception_group.py +++ b/Lib/test/test_exception_group.py @@ -114,7 +114,7 @@ def test_ExceptionGroup(self): eg = BaseExceptionGroup( 'nested', [eg, ValueError(1), eg, TypeError(2)]) - self.assertEqual(str(eg), "nested (group of 6 exceptions)") + self.assertEqual(str(eg), "nested (group of 4 exceptions)") self.assertEqual(repr(eg), "ExceptionGroup('nested', " "[ExceptionGroup('flat', " @@ -136,7 +136,7 @@ def test_BaseExceptionGroup(self): eg = BaseExceptionGroup( 'nested', [eg, ValueError(1), eg]) - self.assertEqual(str(eg), "nested (group of 5 exceptions)") + self.assertEqual(str(eg), "nested (group of 3 exceptions)") self.assertEqual(repr(eg), "BaseExceptionGroup('nested', " "[BaseExceptionGroup('flat', " @@ -158,7 +158,7 @@ class MyEG(ExceptionGroup): eg = MyEG( 'nested', [eg, ValueError(1), eg, TypeError(2)]) - self.assertEqual(str(eg), "nested (group of 6 exceptions)") + self.assertEqual(str(eg), "nested (group of 4 exceptions)") self.assertEqual(repr(eg), ( "MyEG('nested', " "[MyEG('flat', [ValueError(1), TypeError(2)]), " diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 36ffeecc023e9c..3365f6e8f7eac6 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -1519,7 +1519,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n' f' | raise EG("eg", [VE(1), exc, VE(4)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: eg (group of 4 exceptions)\n' + f' | ExceptionGroup: eg (group of 3 exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 1\n' f' +---------------- 2 ----------------\n' @@ -1605,43 +1605,43 @@ def test_exception_group_depth_limit(self): f'eg{i}', [ValueError(i), exc, ValueError(-i)]) - expected = (' | ExceptionGroup: eg999 (group of 2001 exceptions)\n' + expected = (' | ExceptionGroup: eg999 (group of 3 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 999\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg998 (group of 1999 exceptions)\n' + ' | ExceptionGroup: eg998 (group of 3 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 998\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg997 (group of 1997 exceptions)\n' + ' | ExceptionGroup: eg997 (group of 3 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 997\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg996 (group of 1995 exceptions)\n' + ' | ExceptionGroup: eg996 (group of 3 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 996\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg995 (group of 1993 exceptions)\n' + ' | ExceptionGroup: eg995 (group of 3 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 995\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg994 (group of 1991 exceptions)\n' + ' | ExceptionGroup: eg994 (group of 3 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 994\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg993 (group of 1989 exceptions)\n' + ' | ExceptionGroup: eg993 (group of 3 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 993\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg992 (group of 1987 exceptions)\n' + ' | ExceptionGroup: eg992 (group of 3 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 992\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg991 (group of 1985 exceptions)\n' + ' | ExceptionGroup: eg991 (group of 3 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 991\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg990 (group of 1983 exceptions)\n' + ' | ExceptionGroup: eg990 (group of 3 exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 990\n' ' +---------------- 2 ----------------\n' @@ -2460,7 +2460,7 @@ def test_exception_group_construction(self): def test_exception_group_format_exception_only(self): teg = traceback.TracebackException(*self.eg_info) formatted = ''.join(teg.format_exception_only()).split('\n') - expected = "ExceptionGroup: eg2 (group of 3 exceptions)\n".split('\n') + expected = "ExceptionGroup: eg2 (group of 2 exceptions)\n".split('\n') self.assertEqual(formatted, expected) @@ -2476,7 +2476,7 @@ def test_exception_group_format(self): f' | File "{__file__}", line {lno_g+23}, in _get_exception_group', f' | raise ExceptionGroup("eg2", [exc3, exc4])', f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^', - f' | ExceptionGroup: eg2 (group of 3 exceptions)', + f' | ExceptionGroup: eg2 (group of 2 exceptions)', f' +-+---------------- 1 ----------------', f' | Exception Group Traceback (most recent call last):', f' | File "{__file__}", line {lno_g+16}, in _get_exception_group', @@ -2531,7 +2531,7 @@ def test_max_group_width(self): formatted = ''.join(teg.format()).split('\n') expected = [ - f' | ExceptionGroup: eg (group of 13 exceptions)', + f' | ExceptionGroup: eg (group of 2 exceptions)', f' +-+---------------- 1 ----------------', f' | ExceptionGroup: eg1 (group of 3 exceptions)', f' +-+---------------- 1 ----------------', @@ -2563,11 +2563,11 @@ def test_max_group_depth(self): formatted = ''.join(teg.format()).split('\n') expected = [ - f' | ExceptionGroup: exc (group of 7 exceptions)', + f' | ExceptionGroup: exc (group of 3 exceptions)', f' +-+---------------- 1 ----------------', f' | ValueError: -2', f' +---------------- 2 ----------------', - f' | ExceptionGroup: exc (group of 5 exceptions)', + f' | ExceptionGroup: exc (group of 3 exceptions)', f' +-+---------------- 1 ----------------', f' | ValueError: -1', f' +---------------- 2 ----------------', diff --git a/Objects/exceptions.c b/Objects/exceptions.c index fb554f74b7e699..11df672953e093 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -714,7 +714,6 @@ BaseExceptionGroup_new(PyTypeObject *type, PyObject *args, PyObject *kwds) goto error; } - Py_ssize_t leaf_count = 0; bool nested_base_exceptions = false; for (Py_ssize_t i = 0; i < numexcs; i++) { PyObject *exc = PyTuple_GET_ITEM(exceptions, i); @@ -735,12 +734,6 @@ BaseExceptionGroup_new(PyTypeObject *type, PyObject *args, PyObject *kwds) else if (is_nonbase_exception == 0) { nested_base_exceptions = true; } - if (_PyBaseExceptionGroup_Check(exc)) { - leaf_count += ((PyBaseExceptionGroupObject *)exc)->leaf_count; - } - else { - leaf_count++; - } } PyTypeObject *cls = type; @@ -777,7 +770,6 @@ BaseExceptionGroup_new(PyTypeObject *type, PyObject *args, PyObject *kwds) self->msg = Py_NewRef(message); self->excs = exceptions; - self->leaf_count = leaf_count; return (PyObject*)self; error: Py_DECREF(exceptions); @@ -845,9 +837,11 @@ BaseExceptionGroup_str(PyBaseExceptionGroupObject *self) assert(self->msg); assert(PyUnicode_Check(self->msg)); + assert(PyTuple_CheckExact(self->excs)); + Py_ssize_t num_excs = PyTuple_Size(self->excs); return PyUnicode_FromFormat( "%S (group of %zd exception%s)", - self->msg, self->leaf_count, self->leaf_count > 1 ? "s" : ""); + self->msg, num_excs, num_excs > 1 ? "s" : ""); } static PyObject * From 4509b99935c91067e3d43d4d6fa80bae74fb1d63 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Sun, 20 Feb 2022 22:54:39 +0000 Subject: [PATCH 3/4] (group of X exceptions) --> (X sub-exceptions) --- Lib/test/test_exception_group.py | 12 +++---- Lib/test/test_traceback.py | 56 ++++++++++++++++---------------- Objects/exceptions.c | 2 +- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Lib/test/test_exception_group.py b/Lib/test/test_exception_group.py index ccdb3e29fd8088..8a55c826b83283 100644 --- a/Lib/test/test_exception_group.py +++ b/Lib/test/test_exception_group.py @@ -107,14 +107,14 @@ def test_ExceptionGroup(self): eg = BaseExceptionGroup( 'flat', [ValueError(1), TypeError(2)]) - self.assertEqual(str(eg), "flat (group of 2 exceptions)") + 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 (group of 4 exceptions)") + self.assertEqual(str(eg), "nested (4 sub-exceptions)") self.assertEqual(repr(eg), "ExceptionGroup('nested', " "[ExceptionGroup('flat', " @@ -127,7 +127,7 @@ def test_BaseExceptionGroup(self): eg = BaseExceptionGroup( 'flat', [ValueError(1), KeyboardInterrupt(2)]) - self.assertEqual(str(eg), "flat (group of 2 exceptions)") + self.assertEqual(str(eg), "flat (2 sub-exceptions)") self.assertEqual(repr(eg), "BaseExceptionGroup(" "'flat', " @@ -136,7 +136,7 @@ def test_BaseExceptionGroup(self): eg = BaseExceptionGroup( 'nested', [eg, ValueError(1), eg]) - self.assertEqual(str(eg), "nested (group of 3 exceptions)") + self.assertEqual(str(eg), "nested (3 sub-exceptions)") self.assertEqual(repr(eg), "BaseExceptionGroup('nested', " "[BaseExceptionGroup('flat', " @@ -152,13 +152,13 @@ class MyEG(ExceptionGroup): eg = MyEG( 'flat', [ValueError(1), TypeError(2)]) - self.assertEqual(str(eg), "flat (group of 2 exceptions)") + 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 (group of 4 exceptions)") + self.assertEqual(str(eg), "nested (4 sub-exceptions)") self.assertEqual(repr(eg), ( "MyEG('nested', " "[MyEG('flat', [ValueError(1), TypeError(2)]), " diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 3365f6e8f7eac6..75d668df64d4c8 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -1403,7 +1403,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n' f' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: eg (group of 2 exceptions)\n' + f' | ExceptionGroup: eg (2 sub-exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 1\n' f' +---------------- 2 ----------------\n' @@ -1425,7 +1425,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n' f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: eg1 (group of 2 exceptions)\n' + f' | ExceptionGroup: eg1 (2 sub-exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 1\n' f' +---------------- 2 ----------------\n' @@ -1441,7 +1441,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n' f' | raise EG("eg2", [ValueError(3), TypeError(4)]) from e\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: eg2 (group of 2 exceptions)\n' + f' | ExceptionGroup: eg2 (2 sub-exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 3\n' f' +---------------- 2 ----------------\n' @@ -1467,7 +1467,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 4}, in exc\n' f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: eg1 (group of 2 exceptions)\n' + f' | ExceptionGroup: eg1 (2 sub-exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 1\n' f' +---------------- 2 ----------------\n' @@ -1480,7 +1480,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n' f' | raise EG("eg2", [ValueError(3), TypeError(4)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: eg2 (group of 2 exceptions)\n' + f' | ExceptionGroup: eg2 (2 sub-exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 3\n' f' +---------------- 2 ----------------\n' @@ -1519,7 +1519,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n' f' | raise EG("eg", [VE(1), exc, VE(4)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: eg (group of 3 exceptions)\n' + f' | ExceptionGroup: eg (3 sub-exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 1\n' f' +---------------- 2 ----------------\n' @@ -1527,7 +1527,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n' f' | raise EG("nested", [TE(2), TE(3)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: nested (group of 2 exceptions)\n' + f' | ExceptionGroup: nested (2 sub-exceptions)\n' f' +-+---------------- 1 ----------------\n' f' | TypeError: 2\n' f' +---------------- 2 ----------------\n' @@ -1546,7 +1546,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 11}, in exc\n' f' | raise EG("top", [VE(5)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: top (group of 1 exception)\n' + f' | ExceptionGroup: top (1 sub-exception)\n' f' +-+---------------- 1 ----------------\n' f' | ValueError: 5\n' f' +------------------------------------\n') @@ -1560,7 +1560,7 @@ def test_exception_group_width_limit(self): excs.append(ValueError(i)) eg = ExceptionGroup('eg', excs) - expected = (' | ExceptionGroup: eg (group of 1000 exceptions)\n' + expected = (' | ExceptionGroup: eg (1000 sub-exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 0\n' ' +---------------- 2 ----------------\n' @@ -1605,43 +1605,43 @@ def test_exception_group_depth_limit(self): f'eg{i}', [ValueError(i), exc, ValueError(-i)]) - expected = (' | ExceptionGroup: eg999 (group of 3 exceptions)\n' + expected = (' | ExceptionGroup: eg999 (3 sub-exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 999\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg998 (group of 3 exceptions)\n' + ' | ExceptionGroup: eg998 (3 sub-exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 998\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg997 (group of 3 exceptions)\n' + ' | ExceptionGroup: eg997 (3 sub-exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 997\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg996 (group of 3 exceptions)\n' + ' | ExceptionGroup: eg996 (3 sub-exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 996\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg995 (group of 3 exceptions)\n' + ' | ExceptionGroup: eg995 (3 sub-exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 995\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg994 (group of 3 exceptions)\n' + ' | ExceptionGroup: eg994 (3 sub-exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 994\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg993 (group of 3 exceptions)\n' + ' | ExceptionGroup: eg993 (3 sub-exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 993\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg992 (group of 3 exceptions)\n' + ' | ExceptionGroup: eg992 (3 sub-exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 992\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg991 (group of 3 exceptions)\n' + ' | ExceptionGroup: eg991 (3 sub-exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 991\n' ' +---------------- 2 ----------------\n' - ' | ExceptionGroup: eg990 (group of 3 exceptions)\n' + ' | ExceptionGroup: eg990 (3 sub-exceptions)\n' ' +-+---------------- 1 ----------------\n' ' | ValueError: 990\n' ' +---------------- 2 ----------------\n' @@ -1707,7 +1707,7 @@ def exc(): f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n' f' | raise ExceptionGroup("nested", excs)\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' - f' | ExceptionGroup: nested (group of 2 exceptions)\n' + f' | ExceptionGroup: nested (2 sub-exceptions)\n' f' | >> Multi line note\n' f' | >> Because I am such\n' f' | >> an important exception.\n' @@ -2460,7 +2460,7 @@ def test_exception_group_construction(self): def test_exception_group_format_exception_only(self): teg = traceback.TracebackException(*self.eg_info) formatted = ''.join(teg.format_exception_only()).split('\n') - expected = "ExceptionGroup: eg2 (group of 2 exceptions)\n".split('\n') + expected = "ExceptionGroup: eg2 (2 sub-exceptions)\n".split('\n') self.assertEqual(formatted, expected) @@ -2476,13 +2476,13 @@ def test_exception_group_format(self): f' | File "{__file__}", line {lno_g+23}, in _get_exception_group', f' | raise ExceptionGroup("eg2", [exc3, exc4])', f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^', - f' | ExceptionGroup: eg2 (group of 2 exceptions)', + f' | ExceptionGroup: eg2 (2 sub-exceptions)', f' +-+---------------- 1 ----------------', f' | Exception Group Traceback (most recent call last):', f' | File "{__file__}", line {lno_g+16}, in _get_exception_group', f' | raise ExceptionGroup("eg1", [exc1, exc2])', f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^', - f' | ExceptionGroup: eg1 (group of 2 exceptions)', + f' | ExceptionGroup: eg1 (2 sub-exceptions)', f' +-+---------------- 1 ----------------', f' | Traceback (most recent call last):', f' | File "{__file__}", line {lno_g+9}, in _get_exception_group', @@ -2531,9 +2531,9 @@ def test_max_group_width(self): formatted = ''.join(teg.format()).split('\n') expected = [ - f' | ExceptionGroup: eg (group of 2 exceptions)', + f' | ExceptionGroup: eg (2 sub-exceptions)', f' +-+---------------- 1 ----------------', - f' | ExceptionGroup: eg1 (group of 3 exceptions)', + f' | ExceptionGroup: eg1 (3 sub-exceptions)', f' +-+---------------- 1 ----------------', f' | ValueError: 0', f' +---------------- 2 ----------------', @@ -2542,7 +2542,7 @@ def test_max_group_width(self): f' | and 1 more exception', f' +------------------------------------', f' +---------------- 2 ----------------', - f' | ExceptionGroup: eg2 (group of 10 exceptions)', + f' | ExceptionGroup: eg2 (10 sub-exceptions)', f' +-+---------------- 1 ----------------', f' | TypeError: 0', f' +---------------- 2 ----------------', @@ -2563,11 +2563,11 @@ def test_max_group_depth(self): formatted = ''.join(teg.format()).split('\n') expected = [ - f' | ExceptionGroup: exc (group of 3 exceptions)', + f' | ExceptionGroup: exc (3 sub-exceptions)', f' +-+---------------- 1 ----------------', f' | ValueError: -2', f' +---------------- 2 ----------------', - f' | ExceptionGroup: exc (group of 3 exceptions)', + f' | ExceptionGroup: exc (3 sub-exceptions)', f' +-+---------------- 1 ----------------', f' | ValueError: -1', f' +---------------- 2 ----------------', diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 11df672953e093..977dce50203827 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -840,7 +840,7 @@ BaseExceptionGroup_str(PyBaseExceptionGroupObject *self) assert(PyTuple_CheckExact(self->excs)); Py_ssize_t num_excs = PyTuple_Size(self->excs); return PyUnicode_FromFormat( - "%S (group of %zd exception%s)", + "%S (%zd sub-exception%s)", self->msg, num_excs, num_excs > 1 ? "s" : ""); } From 2155104c56f6665e444dd39e250e322553c3a88a Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Tue, 22 Feb 2022 17:18:38 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core and Builtins/2022-02-22-17-18-36.bpo-46729.ZwGTFq.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-02-22-17-18-36.bpo-46729.ZwGTFq.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-02-22-17-18-36.bpo-46729.ZwGTFq.rst b/Misc/NEWS.d/next/Core and Builtins/2022-02-22-17-18-36.bpo-46729.ZwGTFq.rst new file mode 100644 index 00000000000000..dbfb05fcfd9eb5 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-02-22-17-18-36.bpo-46729.ZwGTFq.rst @@ -0,0 +1 @@ +Add number of sub-exceptions to :meth:`BaseException.__str__`.