From a84a178e486be6cbee27d60b6ee1092b9bd968e6 Mon Sep 17 00:00:00 2001 From: Locked-chess-official <13140752715@163.com> Date: Sat, 4 Oct 2025 22:34:42 +0800 Subject: [PATCH 01/14] add the support for BaseExceptionGroup --- Lib/idlelib/run.py | 102 +++++++++++++++--- ...-10-04-22-34-06.gh-issue-139551.hJDxw6.rst | 1 + 2 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2025-10-04-22-34-06.gh-issue-139551.hJDxw6.rst diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index a30db99a619a93..734accc13ebadb 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -250,30 +250,98 @@ def print_exception(): sys.last_exc = val seen = set() - def print_exc(typ, exc, tb): + def print_exc(typ, exc, tb, prefix=""): seen.add(id(exc)) context = exc.__context__ cause = exc.__cause__ + exclude = ("run.py", "rpc.py", "threading.py", "queue.py", + "debugger_r.py", "bdb.py") if cause is not None and id(cause) not in seen: - print_exc(type(cause), cause, cause.__traceback__) - print("\nThe above exception was the direct cause " - "of the following exception:\n", file=efile) + print_exc(type(cause), cause, cause.__traceback__, prefix) + if prefix: + print(f"{prefix}|\n{prefix}| The above exception was the direct cause " + f"of the following exception:\n{prefix}|", file=efile) + else: + print("\nThe above exception was the direct cause " + "of the following exception:\n", file=efile) elif (context is not None and not exc.__suppress_context__ and id(context) not in seen): - print_exc(type(context), context, context.__traceback__) - print("\nDuring handling of the above exception, " - "another exception occurred:\n", file=efile) - if tb: - tbe = traceback.extract_tb(tb) - print('Traceback (most recent call last):', file=efile) - exclude = ("run.py", "rpc.py", "threading.py", "queue.py", - "debugger_r.py", "bdb.py") - cleanup_traceback(tbe, exclude) - traceback.print_list(tbe, file=efile) - lines = get_message_lines(typ, exc, tb) - for line in lines: - print(line, end='', file=efile) + print_exc(type(context), context, context.__traceback__, prefix) + if prefix: + print(f"{prefix}|\n{prefix}| During handling of the above exception, " + f"another exception occurred:\n{prefix}|", file=efile) + else: + print("\nDuring handling of the above exception, " + "another exception occurred:\n", file=efile) + if isinstance(exc, BaseExceptionGroup): + if tb: + if not prefix: + print(" + Exception Group Traceback (most recent call last):", file=efile) + else: + print(f"{prefix}| Exception Group Traceback (most recent call last):", file=efile) + tbe = traceback.extract_tb(tb) + cleanup_traceback(tbe, exclude) + for line in traceback.format_list(tbe): + for subline in line.rstrip().splitlines(): + if not prefix: + print(f" | {subline}", file=efile) + else: + print(f"{prefix}| {subline}", file=efile) + lines = get_message_lines(typ, exc, tb) + for line in lines: + if not prefix: + print(f" | {line}", end="", file=efile) + else: + print(f"{prefix}| {line}", end="", file=efile) + + for i, sub in enumerate(exc.exceptions, 1): + if i == 1: + first_line_pre = "+-" + else: + first_line_pre = " " + if not prefix: + print(f" {first_line_pre}+---------------- {i} ----------------", file=efile) + else: + print(f"{prefix}{first_line_pre}+---------------- {i} ----------------", file=efile) + if id(sub) not in seen: + if not prefix: + print_exc(type(sub), sub, sub.__traceback__, " ") + else: + print_exc(type(sub), sub, sub.__traceback__, prefix + " ") + need_print_underline = not isinstance(sub, BaseExceptionGroup) + else: + if not prefix: + print("f | ") + else: + print(f"{prefix} | ") + need_print_underline = True + if need_print_underline: + if not prefix: + print(" +------------------------------------", file=efile) + else: + print(f" {prefix}+------------------------------------", file=efile) + + else: + if tb: + if prefix: + print(f"{prefix}| Traceback (most recent call last):", file=efile) + else: + print("Traceback (most recent call last):", file=efile) + tbe = traceback.extract_tb(tb) + cleanup_traceback(tbe, exclude) + if prefix: + for line in traceback.format_list(tbe): + for subline in line.rstrip().splitlines(): + print(f"{prefix}| {subline}", file=efile) + else: + traceback.print_list(tbe, file=efile) + lines = get_message_lines(typ, exc, tb) + for line in lines: + if prefix: + print(f"{prefix}| {line}", end="", file=efile) + else: + print(line, end='', file=efile) print_exc(typ, val, tb) diff --git a/Misc/NEWS.d/next/IDLE/2025-10-04-22-34-06.gh-issue-139551.hJDxw6.rst b/Misc/NEWS.d/next/IDLE/2025-10-04-22-34-06.gh-issue-139551.hJDxw6.rst new file mode 100644 index 00000000000000..1718f83289b9a6 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2025-10-04-22-34-06.gh-issue-139551.hJDxw6.rst @@ -0,0 +1 @@ +Add the support with BaseExceptionGroup in IDLE From 99936772db6af85bc49a2873927c73e2a3968df3 Mon Sep 17 00:00:00 2001 From: Locked-chess-official <13140752715@163.com> Date: Sun, 5 Oct 2025 17:15:36 +0800 Subject: [PATCH 02/14] 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> --- Lib/idlelib/run.py | 6 ++---- .../IDLE/2025-10-04-22-34-06.gh-issue-139551.hJDxw6.rst | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 734accc13ebadb..e61332589b850a 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -282,12 +282,10 @@ def print_exc(typ, exc, tb, prefix=""): print(f"{prefix}| Exception Group Traceback (most recent call last):", file=efile) tbe = traceback.extract_tb(tb) cleanup_traceback(tbe, exclude) + prefix2 = prefix or " " for line in traceback.format_list(tbe): for subline in line.rstrip().splitlines(): - if not prefix: - print(f" | {subline}", file=efile) - else: - print(f"{prefix}| {subline}", file=efile) + print(f"{prefix2}| {subline}", file=efile) lines = get_message_lines(typ, exc, tb) for line in lines: if not prefix: diff --git a/Misc/NEWS.d/next/IDLE/2025-10-04-22-34-06.gh-issue-139551.hJDxw6.rst b/Misc/NEWS.d/next/IDLE/2025-10-04-22-34-06.gh-issue-139551.hJDxw6.rst index 1718f83289b9a6..5ea1dfc9b5100d 100644 --- a/Misc/NEWS.d/next/IDLE/2025-10-04-22-34-06.gh-issue-139551.hJDxw6.rst +++ b/Misc/NEWS.d/next/IDLE/2025-10-04-22-34-06.gh-issue-139551.hJDxw6.rst @@ -1 +1 @@ -Add the support with BaseExceptionGroup in IDLE +Support rendering :exc:`BaseExceptionGroup` in IDLE. From 147677806ee978ff8728841eb7c5e7229c552cb3 Mon Sep 17 00:00:00 2001 From: Locked-chess-official <13140752715@163.com> Date: Sun, 5 Oct 2025 18:27:06 +0800 Subject: [PATCH 03/14] require change --- Lib/idlelib/run.py | 112 +++++++----------- ...10-05-18-26-43.gh-issue-139551.TX9BRc.rst} | 0 2 files changed, 45 insertions(+), 67 deletions(-) rename Misc/NEWS.d/next/IDLE/{2025-10-04-22-34-06.gh-issue-139551.hJDxw6.rst => 2025-10-05-18-26-43.gh-issue-139551.TX9BRc.rst} (100%) diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index e61332589b850a..3cbfaa33cac54a 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -250,82 +250,63 @@ def print_exception(): sys.last_exc = val seen = set() + def print_exc_group(typ, exc, tb, prefix=""): + prefix2 = prefix or " " + if tb: + if not prefix: + print(" + Exception Group Traceback (most recent call last):", file=efile) + else: + print(f"{prefix}| Exception Group Traceback (most recent call last):", file=efile) + tbe = traceback.extract_tb(tb) + cleanup_traceback(tbe, exclude) + + for line in traceback.format_list(tbe): + for subline in line.rstrip().splitlines(): + print(f"{prefix2}| {subline}", file=efile) + lines = get_message_lines(typ, exc, tb) + for line in lines: + print(f"{prefix2}| {line}", end="", file=efile) + for i, sub in enumerate(exc.exceptions, 1): + if i == 1: + first_line_pre = "+-" + else: + first_line_pre = " " + print(f"{prefix2}{first_line_pre}+---------------- {i} ----------------", file=efile) + if id(sub) not in seen: + if not prefix: + print_exc(type(sub), sub, sub.__traceback__, " ") + else: + print_exc(type(sub), sub, sub.__traceback__, prefix + " ") + need_print_underline = not isinstance(sub, BaseExceptionGroup) + else: + print(f"{prefix2} | ") + need_print_underline = True + if need_print_underline: + print(f"{prefix2} +------------------------------------", file=efile) + def print_exc(typ, exc, tb, prefix=""): seen.add(id(exc)) context = exc.__context__ cause = exc.__cause__ exclude = ("run.py", "rpc.py", "threading.py", "queue.py", "debugger_r.py", "bdb.py") + prifix2 = f"{prefix}| " if prefix else "" if cause is not None and id(cause) not in seen: - print_exc(type(cause), cause, cause.__traceback__, prefix) - if prefix: - print(f"{prefix}|\n{prefix}| The above exception was the direct cause " - f"of the following exception:\n{prefix}|", file=efile) - else: - print("\nThe above exception was the direct cause " - "of the following exception:\n", file=efile) + print_exc(type(cause), cause, cause.__traceback__, prefix) + print(f"{prefix2}\n{prefix2}The above exception was the direct cause " + f"of the following exception:\n{prefix2}", file=efile) elif (context is not None and not exc.__suppress_context__ and id(context) not in seen): print_exc(type(context), context, context.__traceback__, prefix) - if prefix: - print(f"{prefix}|\n{prefix}| During handling of the above exception, " - f"another exception occurred:\n{prefix}|", file=efile) - else: - print("\nDuring handling of the above exception, " - "another exception occurred:\n", file=efile) + + print(f"{prefix2}\n{prefix2}During handling of the above exception, " + f"another exception occurred:\n{prefix2}", file=efile) if isinstance(exc, BaseExceptionGroup): - if tb: - if not prefix: - print(" + Exception Group Traceback (most recent call last):", file=efile) - else: - print(f"{prefix}| Exception Group Traceback (most recent call last):", file=efile) - tbe = traceback.extract_tb(tb) - cleanup_traceback(tbe, exclude) - prefix2 = prefix or " " - for line in traceback.format_list(tbe): - for subline in line.rstrip().splitlines(): - print(f"{prefix2}| {subline}", file=efile) - lines = get_message_lines(typ, exc, tb) - for line in lines: - if not prefix: - print(f" | {line}", end="", file=efile) - else: - print(f"{prefix}| {line}", end="", file=efile) - - for i, sub in enumerate(exc.exceptions, 1): - if i == 1: - first_line_pre = "+-" - else: - first_line_pre = " " - if not prefix: - print(f" {first_line_pre}+---------------- {i} ----------------", file=efile) - else: - print(f"{prefix}{first_line_pre}+---------------- {i} ----------------", file=efile) - if id(sub) not in seen: - if not prefix: - print_exc(type(sub), sub, sub.__traceback__, " ") - else: - print_exc(type(sub), sub, sub.__traceback__, prefix + " ") - need_print_underline = not isinstance(sub, BaseExceptionGroup) - else: - if not prefix: - print("f | ") - else: - print(f"{prefix} | ") - need_print_underline = True - if need_print_underline: - if not prefix: - print(" +------------------------------------", file=efile) - else: - print(f" {prefix}+------------------------------------", file=efile) - + print_exc_group(typ, exc, tb, prefix=prefix) else: - if tb: - if prefix: - print(f"{prefix}| Traceback (most recent call last):", file=efile) - else: - print("Traceback (most recent call last):", file=efile) + if tb: + print(f"{prefix2}Traceback (most recent call last):", file=efile) tbe = traceback.extract_tb(tb) cleanup_traceback(tbe, exclude) if prefix: @@ -336,10 +317,7 @@ def print_exc(typ, exc, tb, prefix=""): traceback.print_list(tbe, file=efile) lines = get_message_lines(typ, exc, tb) for line in lines: - if prefix: - print(f"{prefix}| {line}", end="", file=efile) - else: - print(line, end='', file=efile) + print(f"{prefix2}{line}", end="", file=efile) print_exc(typ, val, tb) diff --git a/Misc/NEWS.d/next/IDLE/2025-10-04-22-34-06.gh-issue-139551.hJDxw6.rst b/Misc/NEWS.d/next/IDLE/2025-10-05-18-26-43.gh-issue-139551.TX9BRc.rst similarity index 100% rename from Misc/NEWS.d/next/IDLE/2025-10-04-22-34-06.gh-issue-139551.hJDxw6.rst rename to Misc/NEWS.d/next/IDLE/2025-10-05-18-26-43.gh-issue-139551.TX9BRc.rst From f32c29af4065245b29be2acc7cf82a0f7edf2cd7 Mon Sep 17 00:00:00 2001 From: Locked-chess-official <13140752715@163.com> Date: Sun, 5 Oct 2025 18:33:47 +0800 Subject: [PATCH 04/14] bug fix --- Lib/idlelib/run.py | 6 +++--- ...c.rst => 2025-10-05-18-33-15.gh-issue-139551.TX9BRc.rst} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename Misc/NEWS.d/next/IDLE/{2025-10-05-18-26-43.gh-issue-139551.TX9BRc.rst => 2025-10-05-18-33-15.gh-issue-139551.TX9BRc.rst} (100%) diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 3cbfaa33cac54a..32792190de39d4 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -249,6 +249,8 @@ def print_exception(): sys.last_type, sys.last_value, sys.last_traceback = excinfo sys.last_exc = val seen = set() + exclude = ("run.py", "rpc.py", "threading.py", "queue.py", + "debugger_r.py", "bdb.py") def print_exc_group(typ, exc, tb, prefix=""): prefix2 = prefix or " " @@ -287,9 +289,7 @@ def print_exc_group(typ, exc, tb, prefix=""): def print_exc(typ, exc, tb, prefix=""): seen.add(id(exc)) context = exc.__context__ - cause = exc.__cause__ - exclude = ("run.py", "rpc.py", "threading.py", "queue.py", - "debugger_r.py", "bdb.py") + cause = exc.__cause__ prifix2 = f"{prefix}| " if prefix else "" if cause is not None and id(cause) not in seen: print_exc(type(cause), cause, cause.__traceback__, prefix) diff --git a/Misc/NEWS.d/next/IDLE/2025-10-05-18-26-43.gh-issue-139551.TX9BRc.rst b/Misc/NEWS.d/next/IDLE/2025-10-05-18-33-15.gh-issue-139551.TX9BRc.rst similarity index 100% rename from Misc/NEWS.d/next/IDLE/2025-10-05-18-26-43.gh-issue-139551.TX9BRc.rst rename to Misc/NEWS.d/next/IDLE/2025-10-05-18-33-15.gh-issue-139551.TX9BRc.rst From 6d30e2f7039bb7baf94248bfdb62abfb9d6ccce0 Mon Sep 17 00:00:00 2001 From: Locked-chess-official <13140752715@163.com> Date: Sun, 5 Oct 2025 18:44:57 +0800 Subject: [PATCH 05/14] wrong name rename --- Lib/idlelib/run.py | 2 +- ...X9BRc.rst => 2025-10-05-18-44-38.gh-issue-139551.TX9BRc.rst} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename Misc/NEWS.d/next/IDLE/{2025-10-05-18-33-15.gh-issue-139551.TX9BRc.rst => 2025-10-05-18-44-38.gh-issue-139551.TX9BRc.rst} (100%) diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 32792190de39d4..5b55d8c28a4a40 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -290,7 +290,7 @@ def print_exc(typ, exc, tb, prefix=""): seen.add(id(exc)) context = exc.__context__ cause = exc.__cause__ - prifix2 = f"{prefix}| " if prefix else "" + prefix2 = f"{prefix}| " if prefix else "" if cause is not None and id(cause) not in seen: print_exc(type(cause), cause, cause.__traceback__, prefix) print(f"{prefix2}\n{prefix2}The above exception was the direct cause " diff --git a/Misc/NEWS.d/next/IDLE/2025-10-05-18-33-15.gh-issue-139551.TX9BRc.rst b/Misc/NEWS.d/next/IDLE/2025-10-05-18-44-38.gh-issue-139551.TX9BRc.rst similarity index 100% rename from Misc/NEWS.d/next/IDLE/2025-10-05-18-33-15.gh-issue-139551.TX9BRc.rst rename to Misc/NEWS.d/next/IDLE/2025-10-05-18-44-38.gh-issue-139551.TX9BRc.rst From e6feee9c6b369ae7f9592381d53852f204e50db8 Mon Sep 17 00:00:00 2001 From: Locked-chess-official <13140752715@163.com> Date: Sun, 5 Oct 2025 19:17:11 +0800 Subject: [PATCH 06/14] remove the extra spaces --- Lib/idlelib/run.py | 17 ++++++++--------- ...5-10-05-19-16-33.gh-issue-139551.TX9BRc.rst} | 0 2 files changed, 8 insertions(+), 9 deletions(-) rename Misc/NEWS.d/next/IDLE/{2025-10-05-18-44-38.gh-issue-139551.TX9BRc.rst => 2025-10-05-19-16-33.gh-issue-139551.TX9BRc.rst} (100%) diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 5b55d8c28a4a40..d5bbb551c4d5c5 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -266,13 +266,13 @@ def print_exc_group(typ, exc, tb, prefix=""): for subline in line.rstrip().splitlines(): print(f"{prefix2}| {subline}", file=efile) lines = get_message_lines(typ, exc, tb) - for line in lines: + for line in lines: print(f"{prefix2}| {line}", end="", file=efile) for i, sub in enumerate(exc.exceptions, 1): if i == 1: first_line_pre = "+-" else: - first_line_pre = " " + first_line_pre = " " print(f"{prefix2}{first_line_pre}+---------------- {i} ----------------", file=efile) if id(sub) not in seen: if not prefix: @@ -280,7 +280,7 @@ def print_exc_group(typ, exc, tb, prefix=""): else: print_exc(type(sub), sub, sub.__traceback__, prefix + " ") need_print_underline = not isinstance(sub, BaseExceptionGroup) - else: + else: print(f"{prefix2} | ") need_print_underline = True if need_print_underline: @@ -289,8 +289,8 @@ def print_exc_group(typ, exc, tb, prefix=""): def print_exc(typ, exc, tb, prefix=""): seen.add(id(exc)) context = exc.__context__ - cause = exc.__cause__ - prefix2 = f"{prefix}| " if prefix else "" + cause = exc.__cause__ + prefix2 = f"{prefix}| " if prefix else "" if cause is not None and id(cause) not in seen: print_exc(type(cause), cause, cause.__traceback__, prefix) print(f"{prefix2}\n{prefix2}The above exception was the direct cause " @@ -298,15 +298,14 @@ def print_exc(typ, exc, tb, prefix=""): elif (context is not None and not exc.__suppress_context__ and id(context) not in seen): - print_exc(type(context), context, context.__traceback__, prefix) - + print_exc(type(context), context, context.__traceback__, prefix) print(f"{prefix2}\n{prefix2}During handling of the above exception, " f"another exception occurred:\n{prefix2}", file=efile) if isinstance(exc, BaseExceptionGroup): print_exc_group(typ, exc, tb, prefix=prefix) else: - if tb: - print(f"{prefix2}Traceback (most recent call last):", file=efile) + if tb: + print(f"{prefix2}Traceback (most recent call last):", file=efile) tbe = traceback.extract_tb(tb) cleanup_traceback(tbe, exclude) if prefix: diff --git a/Misc/NEWS.d/next/IDLE/2025-10-05-18-44-38.gh-issue-139551.TX9BRc.rst b/Misc/NEWS.d/next/IDLE/2025-10-05-19-16-33.gh-issue-139551.TX9BRc.rst similarity index 100% rename from Misc/NEWS.d/next/IDLE/2025-10-05-18-44-38.gh-issue-139551.TX9BRc.rst rename to Misc/NEWS.d/next/IDLE/2025-10-05-19-16-33.gh-issue-139551.TX9BRc.rst From d48c82e0d7829aa98e3b2acb1e4f9c877533dd60 Mon Sep 17 00:00:00 2001 From: Locked-chess-official <13140752715@163.com> Date: Sun, 5 Oct 2025 19:22:31 +0800 Subject: [PATCH 07/14] remove --- Lib/idlelib/run.py | 4 ++-- ...BRc.rst => 2025-10-05-19-22-05.gh-issue-139551.TX9BRc.rst} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename Misc/NEWS.d/next/IDLE/{2025-10-05-19-16-33.gh-issue-139551.TX9BRc.rst => 2025-10-05-19-22-05.gh-issue-139551.TX9BRc.rst} (100%) diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index d5bbb551c4d5c5..8820b1fc348a6d 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -292,13 +292,13 @@ def print_exc(typ, exc, tb, prefix=""): cause = exc.__cause__ prefix2 = f"{prefix}| " if prefix else "" if cause is not None and id(cause) not in seen: - print_exc(type(cause), cause, cause.__traceback__, prefix) + print_exc(type(cause), cause, cause.__traceback__, prefix) print(f"{prefix2}\n{prefix2}The above exception was the direct cause " f"of the following exception:\n{prefix2}", file=efile) elif (context is not None and not exc.__suppress_context__ and id(context) not in seen): - print_exc(type(context), context, context.__traceback__, prefix) + print_exc(type(context), context, context.__traceback__, prefix) print(f"{prefix2}\n{prefix2}During handling of the above exception, " f"another exception occurred:\n{prefix2}", file=efile) if isinstance(exc, BaseExceptionGroup): diff --git a/Misc/NEWS.d/next/IDLE/2025-10-05-19-16-33.gh-issue-139551.TX9BRc.rst b/Misc/NEWS.d/next/IDLE/2025-10-05-19-22-05.gh-issue-139551.TX9BRc.rst similarity index 100% rename from Misc/NEWS.d/next/IDLE/2025-10-05-19-16-33.gh-issue-139551.TX9BRc.rst rename to Misc/NEWS.d/next/IDLE/2025-10-05-19-22-05.gh-issue-139551.TX9BRc.rst From 3aa213783ed019faaa752ee5837290af28194bed Mon Sep 17 00:00:00 2001 From: Locked-chess-official <13140752715@163.com> Date: Sun, 5 Oct 2025 19:28:06 +0800 Subject: [PATCH 08/14] remove extra line --- Lib/idlelib/run.py | 3 +-- .../next/IDLE/2025-10-05-19-27-50.gh-issue-139551.TX9BRc.rst | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2025-10-05-19-27-50.gh-issue-139551.TX9BRc.rst diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 8820b1fc348a6d..2bbc2d59a03a77 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -260,8 +260,7 @@ def print_exc_group(typ, exc, tb, prefix=""): else: print(f"{prefix}| Exception Group Traceback (most recent call last):", file=efile) tbe = traceback.extract_tb(tb) - cleanup_traceback(tbe, exclude) - + cleanup_traceback(tbe, exclude) for line in traceback.format_list(tbe): for subline in line.rstrip().splitlines(): print(f"{prefix2}| {subline}", file=efile) diff --git a/Misc/NEWS.d/next/IDLE/2025-10-05-19-27-50.gh-issue-139551.TX9BRc.rst b/Misc/NEWS.d/next/IDLE/2025-10-05-19-27-50.gh-issue-139551.TX9BRc.rst new file mode 100644 index 00000000000000..5ea1dfc9b5100d --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2025-10-05-19-27-50.gh-issue-139551.TX9BRc.rst @@ -0,0 +1 @@ +Support rendering :exc:`BaseExceptionGroup` in IDLE. From 5e1fcf160bea7dcbd7f0e55ed3ef68934ef285aa Mon Sep 17 00:00:00 2001 From: Locked-chess-official <13140752715@163.com> Date: Sun, 5 Oct 2025 19:33:52 +0800 Subject: [PATCH 09/14] push --- Lib/idlelib/run.py | 2 +- .../next/IDLE/2025-10-05-19-27-50.gh-issue-139551.TX9BRc.rst | 1 - ...X9BRc.rst => 2025-10-05-19-33-39.gh-issue-139551.TX9BRc.rst} | 0 3 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 Misc/NEWS.d/next/IDLE/2025-10-05-19-27-50.gh-issue-139551.TX9BRc.rst rename Misc/NEWS.d/next/IDLE/{2025-10-05-19-22-05.gh-issue-139551.TX9BRc.rst => 2025-10-05-19-33-39.gh-issue-139551.TX9BRc.rst} (100%) diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 2bbc2d59a03a77..0cd174b36ab33c 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -260,7 +260,7 @@ def print_exc_group(typ, exc, tb, prefix=""): else: print(f"{prefix}| Exception Group Traceback (most recent call last):", file=efile) tbe = traceback.extract_tb(tb) - cleanup_traceback(tbe, exclude) + cleanup_traceback(tbe, exclude) for line in traceback.format_list(tbe): for subline in line.rstrip().splitlines(): print(f"{prefix2}| {subline}", file=efile) diff --git a/Misc/NEWS.d/next/IDLE/2025-10-05-19-27-50.gh-issue-139551.TX9BRc.rst b/Misc/NEWS.d/next/IDLE/2025-10-05-19-27-50.gh-issue-139551.TX9BRc.rst deleted file mode 100644 index 5ea1dfc9b5100d..00000000000000 --- a/Misc/NEWS.d/next/IDLE/2025-10-05-19-27-50.gh-issue-139551.TX9BRc.rst +++ /dev/null @@ -1 +0,0 @@ -Support rendering :exc:`BaseExceptionGroup` in IDLE. diff --git a/Misc/NEWS.d/next/IDLE/2025-10-05-19-22-05.gh-issue-139551.TX9BRc.rst b/Misc/NEWS.d/next/IDLE/2025-10-05-19-33-39.gh-issue-139551.TX9BRc.rst similarity index 100% rename from Misc/NEWS.d/next/IDLE/2025-10-05-19-22-05.gh-issue-139551.TX9BRc.rst rename to Misc/NEWS.d/next/IDLE/2025-10-05-19-33-39.gh-issue-139551.TX9BRc.rst From 1eeaaae671a579d014d6e8b8d85d4d75fb01be04 Mon Sep 17 00:00:00 2001 From: Locked-chess-official <13140752715@163.com> Date: Thu, 16 Oct 2025 15:29:52 +0800 Subject: [PATCH 10/14] Fix exceptions printing logic in run.py --- Lib/idlelib/run.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 0cd174b36ab33c..aa564a8738c06a 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -282,6 +282,7 @@ def print_exc_group(typ, exc, tb, prefix=""): else: print(f"{prefix2} | ") need_print_underline = True + need_print_underline *= (i == len(exc.exceptions)) if need_print_underline: print(f"{prefix2} +------------------------------------", file=efile) From 16abdaf97e195121fbf90a0bd11b3628694aa73a Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Sun, 12 Apr 2026 00:45:45 -0700 Subject: [PATCH 11/14] Fix missing file=efile in IDLE's ExceptionGroup cycle-detection output The print call for the "exception has printed" message in print_exc_group was writing to stdout instead of stderr. Co-Authored-By: Claude Opus 4.6 (1M context) --- Lib/idlelib/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index aa564a8738c06a..c5e0a7e9eef2c6 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -280,7 +280,7 @@ def print_exc_group(typ, exc, tb, prefix=""): print_exc(type(sub), sub, sub.__traceback__, prefix + " ") need_print_underline = not isinstance(sub, BaseExceptionGroup) else: - print(f"{prefix2} | ") + print(f"{prefix2} | ", file=efile) need_print_underline = True need_print_underline *= (i == len(exc.exceptions)) if need_print_underline: From 4ea0d840223095d8675a5cc540f3eb67982ae8b8 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Sun, 12 Apr 2026 00:45:54 -0700 Subject: [PATCH 12/14] Replace boolean multiplication with explicit 'and' in print_exc_group The expression need_print_underline *= (i == len(exc.exceptions)) used bool*bool as a substitute for logical and. Replace with a readable conditional. Co-Authored-By: Claude Opus 4.6 (1M context) --- Lib/idlelib/run.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index c5e0a7e9eef2c6..f653ba9ea802f3 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -282,8 +282,7 @@ def print_exc_group(typ, exc, tb, prefix=""): else: print(f"{prefix2} | ", file=efile) need_print_underline = True - need_print_underline *= (i == len(exc.exceptions)) - if need_print_underline: + if need_print_underline and i == len(exc.exceptions): print(f"{prefix2} +------------------------------------", file=efile) def print_exc(typ, exc, tb, prefix=""): From 576b22328e44962d8c8d35388c75fc41a2776d2f Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Sun, 12 Apr 2026 00:46:25 -0700 Subject: [PATCH 13/14] Add tests for BaseExceptionGroup rendering in IDLE's print_exception Test nested groups, __cause__/__context__ chaining inside groups, and cycle detection for the exception group display code. Co-Authored-By: Claude Opus 4.6 (1M context) --- Lib/idlelib/idle_test/test_run.py | 63 +++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/Lib/idlelib/idle_test/test_run.py b/Lib/idlelib/idle_test/test_run.py index 9a9d3b7b4e219c..b2a7aa877f82f4 100644 --- a/Lib/idlelib/idle_test/test_run.py +++ b/Lib/idlelib/idle_test/test_run.py @@ -82,6 +82,69 @@ def test_get_multiple_message(self, mock): subtests += 1 self.assertEqual(subtests, len(data2)) # All subtests ran? + def _capture_exception(self): + """Call run.print_exception() and return its stderr output.""" + with captured_stderr() as output: + with mock.patch.object(run, 'cleanup_traceback') as ct: + ct.side_effect = lambda t, e: t + run.print_exception() + return output.getvalue() + + @force_not_colorized + def test_print_exception_group_nested(self): + try: + try: + raise ExceptionGroup('inner', [ValueError('v1')]) + except ExceptionGroup as inner: + raise ExceptionGroup('outer', [inner, TypeError('t1')]) + except ExceptionGroup: + tb = self._capture_exception() + + self.assertIn('ExceptionGroup: outer (2 sub-exceptions)', tb) + self.assertIn('ExceptionGroup: inner', tb) + self.assertIn('ValueError: v1', tb) + self.assertIn('TypeError: t1', tb) + # Verify tree structure characters. + self.assertIn('+-+---------------- 1 ----------------', tb) + self.assertIn('+---------------- 2 ----------------', tb) + self.assertIn('+------------------------------------', tb) + + @force_not_colorized + def test_print_exception_group_chaining(self): + # __cause__ on a sub-exception exercises the prefixed + # chaining-message path (margin chars on separator lines). + sub = TypeError('t1') + sub.__cause__ = ValueError('original') + try: + raise ExceptionGroup('eg1', [sub]) + except ExceptionGroup: + tb = self._capture_exception() + self.assertIn('ValueError: original', tb) + self.assertIn('| The above exception was the direct cause', tb) + self.assertIn('ExceptionGroup: eg1', tb) + + # __context__ (implicit chaining) on a sub-exception. + sub = TypeError('t2') + sub.__context__ = ValueError('first') + try: + raise ExceptionGroup('eg2', [sub]) + except ExceptionGroup: + tb = self._capture_exception() + self.assertIn('ValueError: first', tb) + self.assertIn('| During handling of the above exception', tb) + self.assertIn('ExceptionGroup: eg2', tb) + + @force_not_colorized + def test_print_exception_group_seen(self): + shared = ValueError('shared') + try: + raise ExceptionGroup('eg', [shared, shared]) + except ExceptionGroup: + tb = self._capture_exception() + + self.assertIn('ValueError: shared', tb) + self.assertIn('', tb) + # StdioFile tests. class S(str): From 990213c9f77bd3b4a82a288168d4e3d694a647fb Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Sun, 12 Apr 2026 00:47:00 -0700 Subject: [PATCH 14/14] Add max_group_width and max_group_depth limits to IDLE's ExceptionGroup rendering Match the traceback module defaults (max_group_width=15, max_group_depth=10) to prevent pathological exception groups from producing unbounded output or stack overflow via recursion. Co-Authored-By: Claude Opus 4.6 (1M context) --- Lib/idlelib/idle_test/test_run.py | 30 +++++++++++++++++++++++++ Lib/idlelib/run.py | 37 ++++++++++++++++++++++++------- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/Lib/idlelib/idle_test/test_run.py b/Lib/idlelib/idle_test/test_run.py index b2a7aa877f82f4..57bf5559c0fa88 100644 --- a/Lib/idlelib/idle_test/test_run.py +++ b/Lib/idlelib/idle_test/test_run.py @@ -145,6 +145,36 @@ def test_print_exception_group_seen(self): self.assertIn('ValueError: shared', tb) self.assertIn('', tb) + @force_not_colorized + def test_print_exception_group_max_width(self): + excs = [ValueError(f'v{i}') for i in range(20)] + try: + raise ExceptionGroup('eg', excs) + except ExceptionGroup: + tb = self._capture_exception() + + self.assertIn('+---------------- 15 ----------------', tb) + self.assertIn('+---------------- ... ----------------', tb) + self.assertIn('and 5 more exceptions', tb) + self.assertNotIn('+---------------- 16 ----------------', tb) + + @force_not_colorized + def test_print_exception_group_max_depth(self): + def make_nested(depth): + if depth == 0: + return ValueError('leaf') + return ExceptionGroup(f'level{depth}', + [make_nested(depth - 1)]) + + try: + raise make_nested(15) + except ExceptionGroup: + tb = self._capture_exception() + + self.assertIn('... (max_group_depth is 10)', tb) + self.assertIn('ExceptionGroup: level15', tb) + self.assertNotIn('ValueError: leaf', tb) + # StdioFile tests. class S(str): diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index f653ba9ea802f3..e1c40fee8f4805 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -251,9 +251,19 @@ def print_exception(): seen = set() exclude = ("run.py", "rpc.py", "threading.py", "queue.py", "debugger_r.py", "bdb.py") + max_group_width = 15 + max_group_depth = 10 + group_depth = 0 def print_exc_group(typ, exc, tb, prefix=""): + nonlocal group_depth + group_depth += 1 prefix2 = prefix or " " + if group_depth > max_group_depth: + print(f"{prefix2}| ... (max_group_depth is {max_group_depth})", + file=efile) + group_depth -= 1 + return if tb: if not prefix: print(" + Exception Group Traceback (most recent call last):", file=efile) @@ -267,13 +277,23 @@ def print_exc_group(typ, exc, tb, prefix=""): lines = get_message_lines(typ, exc, tb) for line in lines: print(f"{prefix2}| {line}", end="", file=efile) - for i, sub in enumerate(exc.exceptions, 1): - if i == 1: - first_line_pre = "+-" - else: - first_line_pre = " " - print(f"{prefix2}{first_line_pre}+---------------- {i} ----------------", file=efile) - if id(sub) not in seen: + num_excs = len(exc.exceptions) + if num_excs <= max_group_width: + n = num_excs + else: + n = max_group_width + 1 + for i, sub in enumerate(exc.exceptions[:n], 1): + truncated = (i > max_group_width) + first_line_pre = "+-" if i == 1 else " " + title = str(i) if not truncated else '...' + print(f"{prefix2}{first_line_pre}+---------------- {title} ----------------", file=efile) + if truncated: + remaining = num_excs - max_group_width + plural = 's' if remaining > 1 else '' + print(f"{prefix2} | and {remaining} more exception{plural}", + file=efile) + need_print_underline = True + elif id(sub) not in seen: if not prefix: print_exc(type(sub), sub, sub.__traceback__, " ") else: @@ -282,8 +302,9 @@ def print_exc_group(typ, exc, tb, prefix=""): else: print(f"{prefix2} | ", file=efile) need_print_underline = True - if need_print_underline and i == len(exc.exceptions): + if need_print_underline and i == n: print(f"{prefix2} +------------------------------------", file=efile) + group_depth -= 1 def print_exc(typ, exc, tb, prefix=""): seen.add(id(exc))