From e8ea2a34f2c87dc238b7294419ec5319d85bb746 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 10:49:06 -0400 Subject: [PATCH 01/29] Add colorization to unraisable exceptions. --- Python/errors.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Python/errors.c b/Python/errors.c index 81f267b043afaf..f793ae6c6824e9 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1439,8 +1439,6 @@ make_unraisable_hook_args(PyThreadState *tstate, PyObject *exc_type, return args; } - - /* Default implementation of sys.unraisablehook. It can be called to log the exception of a custom sys.unraisablehook. @@ -1485,6 +1483,20 @@ write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type, } } + // Try printing the exception with color + PyObject *print_exception_fn = PyImport_ImportModuleAttrString("traceback", + "_print_exception_bltin"); + if (print_exception_fn != NULL && PyCallable_Check(print_exception_fn)) { + PyObject *result = PyObject_CallOneArg(print_exception_fn, exc_value); + Py_DECREF(print_exception_fn); + Py_XDECREF(result); + if (result != NULL) { + return 0; + } + } + // traceback module failed, fall back to pure C + Py_XDECREF(print_exception_fn); + if (exc_tb != NULL && exc_tb != Py_None) { if (PyTraceBack_Print(exc_tb, file) < 0) { /* continue even if writing the traceback failed */ From 36e38e6d0b17e690b0977d4ed58bc66c4c7b6f95 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 10:50:52 -0400 Subject: [PATCH 02/29] Add blurb. --- .../2025-05-18-10-50-46.gh-issue-134170.J0Hvmi.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-05-18-10-50-46.gh-issue-134170.J0Hvmi.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-18-10-50-46.gh-issue-134170.J0Hvmi.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-18-10-50-46.gh-issue-134170.J0Hvmi.rst new file mode 100644 index 00000000000000..7ea6df8127de8d --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-18-10-50-46.gh-issue-134170.J0Hvmi.rst @@ -0,0 +1,2 @@ +Add colorization to unraisable exceptions by default +(:func:`sys.unraisablehook`). From 6c9d619f418c9fbbc8e6006663e7957845d07fc3 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 10:53:10 -0400 Subject: [PATCH 03/29] Add whatsnew. --- Doc/whatsnew/3.15.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 987cf944972329..3018e301580835 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -77,7 +77,9 @@ Other language changes * Several error messages incorrectly using the term "argument" have been corrected. (Contributed by Stan Ulbrych in :gh:`133382`.) - +* Unraisable exceptions are now highlighted with color by default. This can be + controlled by :ref:`environment variables `. + (Contributed by Peter Bierma in :gh:`134170`.) New modules From d2e621a86118f9188eadb7ee8fa38412c857ce34 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 10:59:00 -0400 Subject: [PATCH 04/29] Clear exceptions upon failure. --- Python/errors.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Python/errors.c b/Python/errors.c index f793ae6c6824e9..09522ce7c145ad 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1495,6 +1495,7 @@ write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type, } } // traceback module failed, fall back to pure C + _PyErr_Clear(tstate); Py_XDECREF(print_exception_fn); if (exc_tb != NULL && exc_tb != Py_None) { From 411406c6218a99583b51fc6064409e77f38789a4 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 11:00:03 -0400 Subject: [PATCH 05/29] Remove stray newline change. --- Python/errors.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/errors.c b/Python/errors.c index 09522ce7c145ad..44881a8c3855d2 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1439,6 +1439,8 @@ make_unraisable_hook_args(PyThreadState *tstate, PyObject *exc_type, return args; } + + /* Default implementation of sys.unraisablehook. It can be called to log the exception of a custom sys.unraisablehook. From 4e9a0b631ac3ce61feee563188354f7bd7c78534 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 11:13:12 -0400 Subject: [PATCH 06/29] Fix signal tests. --- Lib/test/test_signal.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 6d62d6119255a8..fdd1f8609f0e39 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -14,7 +14,7 @@ import unittest from test import support from test.support import ( - is_apple, is_apple_mobile, os_helper, threading_helper + is_apple, is_apple_mobile, os_helper, threading_helper, force_not_colorized ) from test.support.script_helper import assert_python_ok, spawn_python try: @@ -353,6 +353,7 @@ def check_signum(signals): @unittest.skipIf(_testcapi is None, 'need _testcapi') @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") + @force_not_colorized def test_wakeup_write_error(self): # Issue #16105: write() errors in the C signal handler should not # pass silently. From 19db581edb57edb8ab025a6d99985fa5deff4b56 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 11:13:41 -0400 Subject: [PATCH 07/29] Fix cmdline tests. --- Lib/test/test_cmd_line.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 1b40e0d05fe3bc..ec93f3cf715544 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -483,6 +483,7 @@ def test_unmached_quote(self): self.assertRegex(err.decode('ascii', 'ignore'), 'SyntaxError') self.assertEqual(b'', out) + @force_not_colorized def test_stdout_flush_at_shutdown(self): # Issue #5319: if stdout.flush() fails at shutdown, an error should # be printed out. From 6e57c96749a7ad905f4507d957a4f1c35ee8d05b Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 11:14:07 -0400 Subject: [PATCH 08/29] Fix sys tests. --- Lib/test/test_sys.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index fb1c8492a64d38..a41778301ade56 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1485,6 +1485,7 @@ def hook_func(args): expected = None hook_args = None + @force_not_colorized def test_custom_unraisablehook_fail(self): _testcapi = import_helper.import_module('_testcapi') from _testcapi import err_writeunraisable From 06d43ddd606a1a0587284c8e8614eca6e3d06765 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 11:14:36 -0400 Subject: [PATCH 09/29] Oops I lied. --- Lib/test/test_sys.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index a41778301ade56..135f6e359605c7 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1347,6 +1347,7 @@ def test_disable_gil_abi(self): @test.support.cpython_only +@force_not_colorized class UnraisableHookTest(unittest.TestCase): def test_original_unraisablehook(self): _testcapi = import_helper.import_module('_testcapi') From ca0efb4c7f903417ce9af1554ce6e3b9a85c3e1d Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 11:15:18 -0400 Subject: [PATCH 10/29] Fix atexit tests. --- Lib/test/test_threading.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index abe63c10c0ac7c..30d57a244e5faf 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2397,6 +2397,7 @@ def test_atexit_called_once(self): self.assertFalse(err) + @force_not_colorized def test_atexit_after_shutdown(self): # The only way to do this is by registering an atexit within # an atexit, which is intended to raise an exception. From bcfd8115dcf20f05d4ddde742b971bd29cfd448e Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 11:19:44 -0400 Subject: [PATCH 11/29] Skip unneeded @force_not_colorized --- Lib/test/test_sys.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 135f6e359605c7..114f4a7ef3512d 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1486,7 +1486,6 @@ def hook_func(args): expected = None hook_args = None - @force_not_colorized def test_custom_unraisablehook_fail(self): _testcapi = import_helper.import_module('_testcapi') from _testcapi import err_writeunraisable From b3290191cdb9f0b596ba4dd385cbd949c29cd5e7 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 11:21:32 -0400 Subject: [PATCH 12/29] Fix C API tests. --- Lib/test/test_capi/test_exceptions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_exceptions.py b/Lib/test/test_capi/test_exceptions.py index ade55338e63b69..4607c361b13e48 100644 --- a/Lib/test/test_capi/test_exceptions.py +++ b/Lib/test/test_capi/test_exceptions.py @@ -6,7 +6,7 @@ import textwrap from test import support -from test.support import import_helper +from test.support import import_helper, force_not_colorized from test.support.os_helper import TESTFN, TESTFN_UNDECODABLE from test.support.script_helper import assert_python_failure, assert_python_ok from test.support.testcase import ExceptionIsLikeMixin @@ -355,6 +355,7 @@ def test_err_writeunraisable(self): # CRASHES writeunraisable(NULL, hex) # CRASHES writeunraisable(NULL, NULL) + @force_not_colorized def test_err_formatunraisable(self): # Test PyErr_FormatUnraisable() formatunraisable = _testcapi.err_formatunraisable From 8a325d33b1fe10439c362ff5eb81e7debf948a93 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 11:34:23 -0400 Subject: [PATCH 13/29] Fix remaining tests. --- Lib/test/test_concurrent_futures/test_shutdown.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_concurrent_futures/test_shutdown.py b/Lib/test/test_concurrent_futures/test_shutdown.py index 7a4065afd46fc8..ef4368a6d9315c 100644 --- a/Lib/test/test_concurrent_futures/test_shutdown.py +++ b/Lib/test/test_concurrent_futures/test_shutdown.py @@ -49,6 +49,7 @@ def test_interpreter_shutdown(self): self.assertFalse(err) self.assertEqual(out.strip(), b"apple") + @support.force_not_colorized def test_submit_after_interpreter_shutdown(self): # Test the atexit hook for shutdown of worker threads and processes rc, out, err = assert_python_ok('-c', """if 1: From 23604279ee6bc0f4f70cefd3f391e40fd59cccb3 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 11:34:28 -0400 Subject: [PATCH 14/29] Fix remaining tests. --- Lib/test/test_capi/test_exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_capi/test_exceptions.py b/Lib/test/test_capi/test_exceptions.py index 4607c361b13e48..0dd69d1506e655 100644 --- a/Lib/test/test_capi/test_exceptions.py +++ b/Lib/test/test_capi/test_exceptions.py @@ -314,6 +314,7 @@ def test_setfromerrnowithfilename(self): (ENOENT, 'No such file or directory', 'file')) # CRASHES setfromerrnowithfilename(ENOENT, NULL, b'error') + @force_not_colorized def test_err_writeunraisable(self): # Test PyErr_WriteUnraisable() writeunraisable = _testcapi.err_writeunraisable From 9a1d88e35c6085c659c46c7b186970d032d40ae3 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 11:35:46 -0400 Subject: [PATCH 15/29] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/whatsnew/3.15.rst | 1 + Lib/test/test_signal.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 3018e301580835..128cd00382992b 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -77,6 +77,7 @@ Other language changes * Several error messages incorrectly using the term "argument" have been corrected. (Contributed by Stan Ulbrych in :gh:`133382`.) + * Unraisable exceptions are now highlighted with color by default. This can be controlled by :ref:`environment variables `. (Contributed by Peter Bierma in :gh:`134170`.) diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index fdd1f8609f0e39..d6cc22558ec4fa 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -14,7 +14,7 @@ import unittest from test import support from test.support import ( - is_apple, is_apple_mobile, os_helper, threading_helper, force_not_colorized + force_not_colorized, is_apple, is_apple_mobile, os_helper, threading_helper ) from test.support.script_helper import assert_python_ok, spawn_python try: From a8cf99ae7820aa7a8c3d82e40c2db4dfaf0dc58a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 12:08:51 -0400 Subject: [PATCH 16/29] Pass the file instead of looking up sys.stderr again. --- Lib/traceback.py | 4 ++-- Python/errors.c | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/traceback.py b/Lib/traceback.py index 17b082eced6f05..b96a0090b66eda 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -137,8 +137,8 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ BUILTIN_EXCEPTION_LIMIT = object() -def _print_exception_bltin(exc, /): - file = sys.stderr if sys.stderr is not None else sys.__stderr__ +def _print_exception_bltin(exc, file=None, /): + file = file or sys.stderr if sys.stderr is not None else sys.__stderr__ colorize = _colorize.can_colorize(file=file) return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize) diff --git a/Python/errors.c b/Python/errors.c index 44881a8c3855d2..e443c67df1ff2b 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1489,7 +1489,8 @@ write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type, PyObject *print_exception_fn = PyImport_ImportModuleAttrString("traceback", "_print_exception_bltin"); if (print_exception_fn != NULL && PyCallable_Check(print_exception_fn)) { - PyObject *result = PyObject_CallOneArg(print_exception_fn, exc_value); + PyObject *args[2] = {exc_value, file}; + PyObject *result = PyObject_Vectorcall(print_exception_fn, args, 2, NULL); Py_DECREF(print_exception_fn); Py_XDECREF(result); if (result != NULL) { From b32084cbc894b98ed7327bcccd3d0d0f988eebf5 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 12:13:18 -0400 Subject: [PATCH 17/29] Change stupid test case. --- Lib/test/test_capi/test_exceptions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/test/test_capi/test_exceptions.py b/Lib/test/test_capi/test_exceptions.py index 0dd69d1506e655..b60d83e01390fc 100644 --- a/Lib/test/test_capi/test_exceptions.py +++ b/Lib/test/test_capi/test_exceptions.py @@ -314,7 +314,6 @@ def test_setfromerrnowithfilename(self): (ENOENT, 'No such file or directory', 'file')) # CRASHES setfromerrnowithfilename(ENOENT, NULL, b'error') - @force_not_colorized def test_err_writeunraisable(self): # Test PyErr_WriteUnraisable() writeunraisable = _testcapi.err_writeunraisable @@ -356,7 +355,6 @@ def test_err_writeunraisable(self): # CRASHES writeunraisable(NULL, hex) # CRASHES writeunraisable(NULL, NULL) - @force_not_colorized def test_err_formatunraisable(self): # Test PyErr_FormatUnraisable() formatunraisable = _testcapi.err_formatunraisable From 79fb9d2cca0eb42d7428f6777e8276ce054901c1 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 12:28:19 -0400 Subject: [PATCH 18/29] Please let this fix the stupid test. It doesn't repro locally for some reason. --- Lib/test/test_capi/test_exceptions.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_capi/test_exceptions.py b/Lib/test/test_capi/test_exceptions.py index b60d83e01390fc..4967f02b007e06 100644 --- a/Lib/test/test_capi/test_exceptions.py +++ b/Lib/test/test_capi/test_exceptions.py @@ -337,6 +337,10 @@ def test_err_writeunraisable(self): self.assertIsNone(cm.unraisable.err_msg) self.assertIsNone(cm.unraisable.object) + @force_not_colorized + def test_err_writeunraisable_lines(self): + writeunraisable = _testcapi.err_writeunraisable + with (support.swap_attr(sys, 'unraisablehook', None), support.captured_stderr() as stderr): writeunraisable(CustomError('oops!'), hex) @@ -387,6 +391,10 @@ def test_err_formatunraisable(self): self.assertIsNone(cm.unraisable.err_msg) self.assertIsNone(cm.unraisable.object) + @force_not_colorized + def test_err_formatunraisable_lines(self): + formatunraisable = _testcapi.err_formatunraisable + with (support.swap_attr(sys, 'unraisablehook', None), support.captured_stderr() as stderr): formatunraisable(CustomError('oops!'), b'Error in %R', []) From 4c339f3cdb2f0e1c1f8ca160dc6ccfafd9f9e29c Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 18 May 2025 15:09:18 -0400 Subject: [PATCH 19/29] Add a comment to make Benedikt happy. --- Python/errors.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Python/errors.c b/Python/errors.c index e443c67df1ff2b..b005315c41e8c5 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1485,7 +1485,8 @@ write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type, } } - // Try printing the exception with color + // Try printing the exception using the stdlib module. + // If this fails, then we have to use the C implementation. PyObject *print_exception_fn = PyImport_ImportModuleAttrString("traceback", "_print_exception_bltin"); if (print_exception_fn != NULL && PyCallable_Check(print_exception_fn)) { @@ -1494,6 +1495,7 @@ write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type, Py_DECREF(print_exception_fn); Py_XDECREF(result); if (result != NULL) { + // Nothing else to do return 0; } } From 27939640f230ce1d838e7be9c4b32ca1fdf8c0c5 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 2 Jun 2025 06:11:48 -0400 Subject: [PATCH 20/29] Fix the comment about the file parameter. --- Python/errors.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Python/errors.c b/Python/errors.c index b005315c41e8c5..404eb6dc86ecb8 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1445,12 +1445,14 @@ make_unraisable_hook_args(PyThreadState *tstate, PyObject *exc_type, It can be called to log the exception of a custom sys.unraisablehook. - Do nothing if sys.stderr attribute doesn't exist or is set to None. */ + This assumes file is non-NULL. + */ static int write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type, PyObject *exc_value, PyObject *exc_tb, PyObject *err_msg, PyObject *obj, PyObject *file) { + assert(file != NULL && !Py_IsNone(file)); if (obj != NULL && obj != Py_None) { if (err_msg != NULL && err_msg != Py_None) { if (PyFile_WriteObject(err_msg, file, Py_PRINT_RAW) < 0) { From 63892dce42081e4c0e62a5e11f67da83d713f010 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 3 Aug 2025 14:07:02 -0400 Subject: [PATCH 21/29] Fix potential double-decref. --- Python/errors.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Python/errors.c b/Python/errors.c index e393991e7074b2..35c9974cbb0390 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1499,10 +1499,11 @@ write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type, // Nothing else to do return 0; } + } else { + Py_XDECREF(print_exception_fn); } // traceback module failed, fall back to pure C _PyErr_Clear(tstate); - Py_XDECREF(print_exception_fn); if (exc_tb != NULL && exc_tb != Py_None) { if (PyTraceBack_Print(exc_tb, file) < 0) { From 0547bf034c28aaabd5d0d4b0522d0d31531fdc25 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 3 Aug 2025 14:20:27 -0400 Subject: [PATCH 22/29] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/whatsnew/3.15.rst | 1 + Python/errors.c | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 97f9fafd5995a8..54964da473760d 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -204,6 +204,7 @@ Other language changes controlled by :ref:`environment variables `. (Contributed by Peter Bierma in :gh:`134170`.) + New modules =========== diff --git a/Python/errors.c b/Python/errors.c index 35c9974cbb0390..d2d18521785ea1 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1444,14 +1444,15 @@ make_unraisable_hook_args(PyThreadState *tstate, PyObject *exc_type, It can be called to log the exception of a custom sys.unraisablehook. - This assumes file is non-NULL. + This assumes 'file' neither NULL nor None. */ static int write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type, PyObject *exc_value, PyObject *exc_tb, PyObject *err_msg, PyObject *obj, PyObject *file) { - assert(file != NULL && !Py_IsNone(file)); + assert(file != NULL); + assert(!Py_IsNone(file)); if (obj != NULL && obj != Py_None) { if (err_msg != NULL && err_msg != Py_None) { if (PyFile_WriteObject(err_msg, file, Py_PRINT_RAW) < 0) { From 0822b6c577e63991324cac01fc89c5b6d92ab34a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 3 Aug 2025 14:23:18 -0400 Subject: [PATCH 23/29] Avoid checking dangling pointer against NULL. --- Python/errors.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Python/errors.c b/Python/errors.c index 35c9974cbb0390..67e1d35f8a8431 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1493,9 +1493,10 @@ write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type, if (print_exception_fn != NULL && PyCallable_Check(print_exception_fn)) { PyObject *args[2] = {exc_value, file}; PyObject *result = PyObject_Vectorcall(print_exception_fn, args, 2, NULL); + int ok = result != NULL; Py_DECREF(print_exception_fn); Py_XDECREF(result); - if (result != NULL) { + if (ok) { // Nothing else to do return 0; } From 7f36267420fc8a6efcab68f7b068b15aa0f3e6b5 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 3 Aug 2025 14:24:07 -0400 Subject: [PATCH 24/29] Make NEWS entry clearer. --- .../2025-05-18-10-50-46.gh-issue-134170.J0Hvmi.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-18-10-50-46.gh-issue-134170.J0Hvmi.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-18-10-50-46.gh-issue-134170.J0Hvmi.rst index 7ea6df8127de8d..f33a30c7e120dc 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-18-10-50-46.gh-issue-134170.J0Hvmi.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-18-10-50-46.gh-issue-134170.J0Hvmi.rst @@ -1,2 +1 @@ -Add colorization to unraisable exceptions by default -(:func:`sys.unraisablehook`). +Add colorization to :func:`sys.unraisablehook` by default. From 91e1048dc90a891b47e3bcd09182aeab136b671c Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 3 Aug 2025 14:27:48 -0400 Subject: [PATCH 25/29] Update the documentation for sys.unraisablehook(). --- Doc/library/sys.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 52f0af31c68726..ad8ac35d8a6930 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -2152,11 +2152,17 @@ always available. Unless explicitly noted otherwise, all variables are read-only The default hook formats :attr:`!err_msg` and :attr:`!object` as: ``f'{err_msg}: {object!r}'``; use "Exception ignored in" error message - if :attr:`!err_msg` is ``None``. + if :attr:`!err_msg` is ``None``. Similar to the :mod:`traceback` module, + this adds color to exceptions by default. This can be disabled using + :ref:`environment variables `. :func:`sys.unraisablehook` can be overridden to control how unraisable exceptions are handled. + .. versionchanged:: next + + Exceptions are now printed with colorful text. + .. seealso:: :func:`excepthook` which handles uncaught exceptions. From 7deb130f17bd95a7b7d607bed0bab7f906449ea0 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 3 Aug 2025 14:46:04 -0400 Subject: [PATCH 26/29] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/sys.rst | 1 - Python/errors.c | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index ad8ac35d8a6930..771e0f2709a4aa 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -2160,7 +2160,6 @@ always available. Unless explicitly noted otherwise, all variables are read-only exceptions are handled. .. versionchanged:: next - Exceptions are now printed with colorful text. .. seealso:: diff --git a/Python/errors.c b/Python/errors.c index 756c9ff972d961..51cd15dd6bb553 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1444,7 +1444,7 @@ make_unraisable_hook_args(PyThreadState *tstate, PyObject *exc_type, It can be called to log the exception of a custom sys.unraisablehook. - This assumes 'file' neither NULL nor None. + This assumes 'file' is neither NULL nor None. */ static int write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type, @@ -1501,7 +1501,8 @@ write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type, // Nothing else to do return 0; } - } else { + } + else { Py_XDECREF(print_exception_fn); } // traceback module failed, fall back to pure C From f2c8dd7194abf9c967912f54d8a4d05cf46d9a40 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 4 Aug 2025 09:05:10 -0400 Subject: [PATCH 27/29] Update Python/errors.c Co-authored-by: Victor Stinner --- Python/errors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/errors.c b/Python/errors.c index 51cd15dd6bb553..0e8647796b368f 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1494,7 +1494,7 @@ write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type, if (print_exception_fn != NULL && PyCallable_Check(print_exception_fn)) { PyObject *args[2] = {exc_value, file}; PyObject *result = PyObject_Vectorcall(print_exception_fn, args, 2, NULL); - int ok = result != NULL; + int ok = (result != NULL); Py_DECREF(print_exception_fn); Py_XDECREF(result); if (ok) { From 4c3a6e730d725135f9c4ba4dec16bbce02993351 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 4 Aug 2025 09:57:20 -0400 Subject: [PATCH 28/29] Update Lib/traceback.py Co-authored-by: Victor Stinner --- Lib/traceback.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/traceback.py b/Lib/traceback.py index 838460c121767d..318ec13cf91121 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -138,7 +138,8 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ def _print_exception_bltin(exc, file=None, /): - file = file or sys.stderr if sys.stderr is not None else sys.__stderr__ + if file is None: + file = sys.stderr if sys.stderr is not None else sys.__stderr__ colorize = _colorize.can_colorize(file=file) return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize) From 53fb7a8397d50110ce37e797d84cada4abc99c44 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 4 Aug 2025 10:08:28 -0400 Subject: [PATCH 29/29] Newline for readability. --- Python/errors.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Python/errors.c b/Python/errors.c index 0e8647796b368f..2688396004e98b 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1453,6 +1453,7 @@ write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type, { assert(file != NULL); assert(!Py_IsNone(file)); + if (obj != NULL && obj != Py_None) { if (err_msg != NULL && err_msg != Py_None) { if (PyFile_WriteObject(err_msg, file, Py_PRINT_RAW) < 0) {