Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e8ea2a3
Add colorization to unraisable exceptions.
ZeroIntensity May 18, 2025
36e38e6
Add blurb.
ZeroIntensity May 18, 2025
6c9d619
Add whatsnew.
ZeroIntensity May 18, 2025
d2e621a
Clear exceptions upon failure.
ZeroIntensity May 18, 2025
411406c
Remove stray newline change.
ZeroIntensity May 18, 2025
4e9a0b6
Fix signal tests.
ZeroIntensity May 18, 2025
19db581
Fix cmdline tests.
ZeroIntensity May 18, 2025
6e57c96
Fix sys tests.
ZeroIntensity May 18, 2025
06d43dd
Oops I lied.
ZeroIntensity May 18, 2025
ca0efb4
Fix atexit tests.
ZeroIntensity May 18, 2025
bcfd811
Skip unneeded @force_not_colorized
ZeroIntensity May 18, 2025
b329019
Fix C API tests.
ZeroIntensity May 18, 2025
8a325d3
Fix remaining tests.
ZeroIntensity May 18, 2025
2360427
Fix remaining tests.
ZeroIntensity May 18, 2025
9a1d88e
Apply suggestions from code review
ZeroIntensity May 18, 2025
a8cf99a
Pass the file instead of looking up sys.stderr again.
ZeroIntensity May 18, 2025
b32084c
Change stupid test case.
ZeroIntensity May 18, 2025
79fb9d2
Please let this fix the stupid test. It doesn't repro locally for som…
ZeroIntensity May 18, 2025
4c339f3
Add a comment to make Benedikt happy.
ZeroIntensity May 18, 2025
2793964
Fix the comment about the file parameter.
ZeroIntensity Jun 2, 2025
e96740b
Merge branch 'main' into unraisable-color
ZeroIntensity Aug 3, 2025
63892dc
Fix potential double-decref.
ZeroIntensity Aug 3, 2025
0547bf0
Apply suggestions from code review
ZeroIntensity Aug 3, 2025
0822b6c
Avoid checking dangling pointer against NULL.
ZeroIntensity Aug 3, 2025
7f36267
Make NEWS entry clearer.
ZeroIntensity Aug 3, 2025
91e1048
Update the documentation for sys.unraisablehook().
ZeroIntensity Aug 3, 2025
ee2edd5
Merge branch 'unraisable-color' of https://github.com/zerointensity/c…
ZeroIntensity Aug 3, 2025
7deb130
Apply suggestions from code review
ZeroIntensity Aug 3, 2025
f2c8dd7
Update Python/errors.c
ZeroIntensity Aug 4, 2025
4c3a6e7
Update Lib/traceback.py
ZeroIntensity Aug 4, 2025
53fb7a8
Newline for readability.
ZeroIntensity Aug 4, 2025
37d87a0
Merge branch 'main' of https://github.com/python/cpython into unraisa…
ZeroIntensity Aug 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2152,11 +2152,16 @@ 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 <using-on-controlling-color>`.

: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.
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ 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 <using-on-controlling-color>`.
(Contributed by Peter Bierma in :gh:`134170`.)


New modules
===========
Expand Down
10 changes: 9 additions & 1 deletion Lib/test/test_capi/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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', [])
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_cmd_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,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.
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_concurrent_futures/test_shutdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, is_apple, is_apple_mobile, os_helper, threading_helper
)
from test.support.script_helper import assert_python_ok, spawn_python
try:
Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1340,6 +1340,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')
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -2494,6 +2494,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.
Expand Down
5 changes: 3 additions & 2 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,9 @@ 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, /):
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)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add colorization to :func:`sys.unraisablehook` by default.
27 changes: 26 additions & 1 deletion Python/errors.c
Original file line number Diff line number Diff line change
Expand Up @@ -1444,12 +1444,16 @@ 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 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);
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) {
Expand Down Expand Up @@ -1484,6 +1488,27 @@ write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type,
}
}

// 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)) {
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 (ok) {
// Nothing else to do
return 0;
}
}
else {
Py_XDECREF(print_exception_fn);
}
// traceback module failed, fall back to pure C
_PyErr_Clear(tstate);

if (exc_tb != NULL && exc_tb != Py_None) {
if (PyTraceBack_Print(exc_tb, file) < 0) {
/* continue even if writing the traceback failed */
Expand Down
Loading