diff --git a/CHANGES.rst b/CHANGES.rst index 655fd7e..9b55775 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,10 @@ Version history This library adheres to `Semantic Versioning 2.0 `_. +**UNRELEASED** + +- Patch ``sys.unraisablehook`` to properly format exceptiongroups + **1.0.0rc8** - Don't monkey patch anything if ``sys.excepthook`` has been altered diff --git a/src/exceptiongroup/_formatting.py b/src/exceptiongroup/_formatting.py index 63eebf9..f8995b3 100644 --- a/src/exceptiongroup/_formatting.py +++ b/src/exceptiongroup/_formatting.py @@ -252,6 +252,17 @@ def exceptiongroup_excepthook( sys.stderr.write("".join(traceback.format_exception(etype, value, tb))) +def exceptiongroup_unraisablehook(__unraisable: sys.UnraisableHookArgs) -> None: + err_msg = __unraisable.err_msg or "Exception ignored in" + formatted_tb = "".join(traceback.format_tb(__unraisable.exc_traceback)) + formatted_tb += "".join( + traceback.format_exception( + __unraisable.exc_type, __unraisable.exc_value, __unraisable.exc_traceback + ) + ) + sys.stderr.write(f"{err_msg}: {__unraisable.object!r}\n{formatted_tb}") + + if sys.excepthook is sys.__excepthook__: traceback_exception_original_init = traceback.TracebackException.__init__ traceback.TracebackException.__init__ = ( # type: ignore[assignment] @@ -271,3 +282,7 @@ def exceptiongroup_excepthook( traceback.TracebackException, "_format_syntax_error", None ) sys.excepthook = exceptiongroup_excepthook + + # Patch sys.unraisablehook if it's untouched + if sys.version_info >= (3, 8) and sys.unraisablehook is sys.__unraisablehook__: + sys.unraisablehook = exceptiongroup_unraisablehook diff --git a/tests/test_formatting.py b/tests/test_formatting.py index 8b58561..dce3801 100644 --- a/tests/test_formatting.py +++ b/tests/test_formatting.py @@ -1,6 +1,10 @@ +import gc import sys +import pytest + from exceptiongroup import ExceptionGroup +from exceptiongroup._formatting import exceptiongroup_unraisablehook def test_formatting(capsys): @@ -119,3 +123,36 @@ def test_formatting_syntax_error(capsys): SyntaxError: invalid syntax """ ) + + +@pytest.mark.skipif( + sys.version_info < (3, 8), reason="sys.unraisablehook was added in Python 3.8" +) +def test_unraisablehook(capsys, monkeypatch): + # Pytest overrides sys.unraisablehook so we temporarily override that here + monkeypatch.setattr(sys, "unraisablehook", exceptiongroup_unraisablehook) + + class Foo: + def __del__(self): + raise ExceptionGroup("the bad", [Exception("critical debug information")]) + + Foo() + gc.collect() + + lineno = Foo.__del__.__code__.co_firstlineno + module_prefix = "" if sys.version_info >= (3, 11) else "exceptiongroup." + output = capsys.readouterr().err + assert output == ( + f"""\ +Exception ignored in: {Foo.__del__!r} + File "{__file__}", line {lineno + 1}, in __del__ + raise ExceptionGroup("the bad", [Exception("critical debug information")]) + + Exception Group Traceback (most recent call last): + | File "{__file__}", line {lineno + 1}, in __del__ + | raise ExceptionGroup("the bad", [Exception("critical debug information")]) + | {module_prefix}ExceptionGroup: the bad (1 sub-exception) + +-+---------------- 1 ---------------- + | Exception: critical debug information + +------------------------------------ +""" + )