Skip to content

Commit c31e622

Browse files
committed
#17442: Add chained traceback support to InteractiveInterpreter.
Patch by Claudiu Popa.
1 parent 4d75a01 commit c31e622

File tree

5 files changed

+70
-12
lines changed

5 files changed

+70
-12
lines changed

Doc/library/code.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ Interactive Interpreter Objects
114114
because it is within the interpreter object implementation. The output is
115115
written by the :meth:`write` method.
116116

117+
.. versionchanged:: 3.5 The full chained traceback is displayed instead
118+
of just the primary traceback.
119+
117120

118121
.. method:: InteractiveInterpreter.write(data)
119122

Doc/whatsnew/3.5.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,13 @@ New Modules
134134
Improved Modules
135135
================
136136

137+
code
138+
----
139+
140+
* The :func:`code.InteractiveInterpreter.showtraceback` method now prints
141+
the full chained traceback, just like the interactive interpreter
142+
(contributed by Claudiu.Popa in :issue:`17442`).
143+
137144
compileall
138145
----------
139146

Lib/code.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,25 +137,35 @@ def showtraceback(self):
137137
The output is written by self.write(), below.
138138
139139
"""
140+
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
141+
sys.last_traceback = last_tb
140142
try:
141-
type, value, tb = sys.exc_info()
142-
sys.last_type = type
143-
sys.last_value = value
144-
sys.last_traceback = tb
145-
tblist = traceback.extract_tb(tb)
146-
del tblist[:1]
147-
lines = traceback.format_list(tblist)
148-
if lines:
149-
lines.insert(0, "Traceback (most recent call last):\n")
150-
lines.extend(traceback.format_exception_only(type, value))
143+
lines = []
144+
for value, tb in traceback._iter_chain(*ei[1:]):
145+
if isinstance(value, str):
146+
lines.append(value)
147+
lines.append('\n')
148+
continue
149+
if tb:
150+
tblist = traceback.extract_tb(tb)
151+
if tb is last_tb:
152+
# The last traceback includes the frame we
153+
# exec'd in
154+
del tblist[:1]
155+
tblines = traceback.format_list(tblist)
156+
if tblines:
157+
lines.append("Traceback (most recent call last):\n")
158+
lines.extend(tblines)
159+
lines.extend(traceback.format_exception_only(type(value),
160+
value))
151161
finally:
152-
tblist = tb = None
162+
tblist = last_tb = ei = None
153163
if sys.excepthook is sys.__excepthook__:
154164
self.write(''.join(lines))
155165
else:
156166
# If someone has set sys.excepthook, we let that take precedence
157167
# over self.write
158-
sys.excepthook(type, value, tb)
168+
sys.excepthook(type, value, last_tb)
159169

160170
def write(self, data):
161171
"""Write a string.

Lib/test/test_code_module.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"Test InteractiveConsole and InteractiveInterpreter from code module"
22
import sys
33
import unittest
4+
from textwrap import dedent
45
from contextlib import ExitStack
56
from unittest import mock
67
from test import support
@@ -78,6 +79,40 @@ def test_banner(self):
7879
self.console.interact(banner='')
7980
self.assertEqual(len(self.stderr.method_calls), 1)
8081

82+
def test_cause_tb(self):
83+
self.infunc.side_effect = ["raise ValueError('') from AttributeError",
84+
EOFError('Finished')]
85+
self.console.interact()
86+
output = ''.join(''.join(call[1]) for call in self.stderr.method_calls)
87+
expected = dedent("""
88+
AttributeError
89+
90+
The above exception was the direct cause of the following exception:
91+
92+
Traceback (most recent call last):
93+
File "<console>", line 1, in <module>
94+
ValueError
95+
""")
96+
self.assertIn(expected, output)
97+
98+
def test_context_tb(self):
99+
self.infunc.side_effect = ["try: ham\nexcept: eggs\n",
100+
EOFError('Finished')]
101+
self.console.interact()
102+
output = ''.join(''.join(call[1]) for call in self.stderr.method_calls)
103+
expected = dedent("""
104+
Traceback (most recent call last):
105+
File "<console>", line 1, in <module>
106+
NameError: name 'ham' is not defined
107+
108+
During handling of the above exception, another exception occurred:
109+
110+
Traceback (most recent call last):
111+
File "<console>", line 2, in <module>
112+
NameError: name 'eggs' is not defined
113+
""")
114+
self.assertIn(expected, output)
115+
81116

82117
def test_main():
83118
support.run_unittest(TestInteractiveConsole)

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ Core and Builtins
145145
Library
146146
-------
147147

148+
- Issue #17442: InteractiveInterpreter now displays the full chained traceback
149+
in its showtraceback method, to match the built in interactive interpreter.
150+
148151
- Issue #10510: distutils register and upload methods now use HTML standards
149152
compliant CRLF line endings.
150153

0 commit comments

Comments
 (0)