From 2289418934671eb1bef0fe18746a1f2bd8b594cd Mon Sep 17 00:00:00 2001 From: fly1d <309400591+fly1d@users.noreply.github.com> Date: Fri, 30 Oct 2026 21:40:41 +0800 Subject: [PATCH] Fix traceback fallback for ExceptionGroup subclasses --- stackprinter/formatting.py | 3 +- tests/test_formatting.py | 58 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/stackprinter/formatting.py b/stackprinter/formatting.py index 25f5fd8..0b2c912 100644 --- a/stackprinter/formatting.py +++ b/stackprinter/formatting.py @@ -1,6 +1,7 @@ """ Various convnenience methods to walk stacks and concatenate formatted frames """ +import builtins import types import traceback @@ -123,7 +124,7 @@ def format_exc_info(etype, evalue, tb, style='plaintext', add_summary='auto', if etype is None: etype = type(None) - if etype.__name__ == 'ExceptionGroup': + if issubclass(etype, getattr(builtins, 'ExceptionGroup', ())): # Exception groups (new in py 3.11) aren't supported so far, # but at least we fall back on the default message. return ''.join(traceback.format_exception(etype, evalue, tb)) diff --git a/tests/test_formatting.py b/tests/test_formatting.py index 8ff52c4..6d79570 100644 --- a/tests/test_formatting.py +++ b/tests/test_formatting.py @@ -88,3 +88,61 @@ def target(): output = stackprinter.format((ValueError, ValueError("boom"), tb)) assert "target" in output assert "ValueError: boom" in output + + +def _assert_exception_group_format(use_subclass=False, as_tuple=False, + with_traceback=False): + import builtins + import traceback + + import pytest + + group_type = getattr(builtins, 'ExceptionGroup', None) + if group_type is None: + pytest.skip('Exception groups require Python 3.11 or newer') + + outer_type = group_type + if use_subclass: + class CustomGroup(group_type): + pass + outer_type = CustomGroup + + exc = outer_type('outer', [ + ValueError('first nested error'), + group_type('inner', [TypeError('deeply nested error')]), + ]) + if with_traceback: + try: + raise exc + except Exception: + pass + + thing = (type(exc), exc, exc.__traceback__) if as_tuple else exc + expected = ''.join(traceback.format_exception( + type(exc), exc, exc.__traceback__)) + assert stackprinter.format(thing, show_vals=None) == expected + + +def test_exception_group_subclass_instance(): + _assert_exception_group_format(use_subclass=True) + + +def test_exception_group_subclass_exc_info(): + _assert_exception_group_format(use_subclass=True, as_tuple=True) + + +def test_exception_group_subclass_traceback(): + _assert_exception_group_format(use_subclass=True, with_traceback=True) + + +def test_builtin_exception_group_instance(): + _assert_exception_group_format() + + +def test_builtin_exception_group_exc_info(): + _assert_exception_group_format(as_tuple=True) + + +def test_ordinary_exception_instance(): + output = stackprinter.format(ValueError('ordinary error'), show_vals=None) + assert output == 'ValueError: ordinary error'