Skip to content

Commit af3bb21

Browse files
committed
[3.13] pythongh-82378 fix sys.tracebacklimit in pyrepl, approach 2 (pythonGH-123062)
Make sure that pyrepl uses the same logic for sys.tracebacklimit as both the basic repl and the standard sys.excepthook (cherry picked from commit 63603bc) Co-authored-by: CF Bolz-Tereick <[email protected]>
1 parent e4b91b7 commit af3bb21

File tree

4 files changed

+98
-60
lines changed

4 files changed

+98
-60
lines changed

Lib/_pyrepl/console.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,15 @@ def __init__(
162162
self.can_colorize = _colorize.can_colorize()
163163

164164
def showsyntaxerror(self, filename=None, **kwargs):
165-
super().showsyntaxerror(colorize=self.can_colorize, **kwargs)
166-
167-
def showtraceback(self):
168-
super().showtraceback(colorize=self.can_colorize)
165+
super().showsyntaxerror(**kwargs)
166+
167+
def _excepthook(self, typ, value, tb):
168+
import traceback
169+
lines = traceback.format_exception(
170+
typ, value, tb,
171+
colorize=self.can_colorize,
172+
limit=traceback.BUILTIN_EXCEPTION_LIMIT)
173+
self.write(''.join(lines))
169174

170175
def runsource(self, source, filename="<input>", symbol="single"):
171176
try:

Lib/code.py

Lines changed: 54 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -106,72 +106,71 @@ def showsyntaxerror(self, filename=None, **kwargs):
106106
The output is written by self.write(), below.
107107
108108
"""
109-
colorize = kwargs.pop('colorize', False)
110-
type, value, tb = sys.exc_info()
111-
sys.last_exc = value
112-
sys.last_type = type
113-
sys.last_value = value
114-
sys.last_traceback = tb
115-
if filename and type is SyntaxError:
116-
# Work hard to stuff the correct filename in the exception
117-
try:
118-
msg, (dummy_filename, lineno, offset, line) = value.args
119-
except ValueError:
120-
# Not the format we expect; leave it alone
121-
pass
122-
else:
123-
# Stuff in the right filename
124-
value = SyntaxError(msg, (filename, lineno, offset, line))
125-
sys.last_exc = sys.last_value = value
126-
# Set the line of text that the exception refers to
127-
source = kwargs.pop('source', '')
128-
lines = source.splitlines()
129-
if (source and type is SyntaxError
130-
and not value.text and len(lines) >= value.lineno):
131-
value.text = lines[value.lineno - 1]
132-
if sys.excepthook is sys.__excepthook__:
133-
lines = traceback.format_exception_only(type, value, colorize=colorize)
134-
self.write(''.join(lines))
135-
else:
136-
# If someone has set sys.excepthook, we let that take precedence
137-
# over self.write
138-
self._call_excepthook(type, value, tb)
109+
try:
110+
typ, value, tb = sys.exc_info()
111+
if filename and typ is SyntaxError:
112+
# Work hard to stuff the correct filename in the exception
113+
try:
114+
msg, (dummy_filename, lineno, offset, line) = value.args
115+
except ValueError:
116+
# Not the format we expect; leave it alone
117+
pass
118+
else:
119+
# Stuff in the right filename
120+
value = SyntaxError(msg, (filename, lineno, offset, line))
121+
source = kwargs.pop('source', "")
122+
self._showtraceback(typ, value, None, source)
123+
finally:
124+
typ = value = tb = None
139125

140-
def showtraceback(self, **kwargs):
126+
def showtraceback(self):
141127
"""Display the exception that just occurred.
142128
143129
We remove the first stack item because it is our own code.
144130
145131
The output is written by self.write(), below.
146132
147133
"""
148-
colorize = kwargs.pop('colorize', False)
149-
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
150-
sys.last_traceback = last_tb
151-
sys.last_exc = ei[1]
152134
try:
153-
if sys.excepthook is sys.__excepthook__:
154-
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next, colorize=colorize)
155-
self.write(''.join(lines))
156-
else:
157-
# If someone has set sys.excepthook, we let that take precedence
158-
# over self.write
159-
self._call_excepthook(ei[0], ei[1], last_tb)
135+
typ, value, tb = sys.exc_info()
136+
self._showtraceback(typ, value, tb.tb_next, "")
160137
finally:
161-
last_tb = ei = None
138+
typ = value = tb = None
162139

163-
def _call_excepthook(self, typ, value, tb):
164-
try:
165-
sys.excepthook(typ, value, tb)
166-
except SystemExit:
167-
raise
168-
except BaseException as e:
169-
e.__context__ = None
170-
print('Error in sys.excepthook:', file=sys.stderr)
171-
sys.__excepthook__(type(e), e, e.__traceback__.tb_next)
172-
print(file=sys.stderr)
173-
print('Original exception was:', file=sys.stderr)
174-
sys.__excepthook__(typ, value, tb)
140+
141+
def _showtraceback(self, typ, value, tb, source):
142+
sys.last_type = typ
143+
sys.last_traceback = tb
144+
value = value.with_traceback(tb)
145+
# Set the line of text that the exception refers to
146+
lines = source.splitlines()
147+
if (source and typ is SyntaxError
148+
and not value.text and len(lines) >= value.lineno):
149+
value.text = lines[value.lineno - 1]
150+
sys.last_exc = sys.last_value = value
151+
if sys.excepthook is sys.__excepthook__:
152+
self._excepthook(typ, value, tb)
153+
else:
154+
# If someone has set sys.excepthook, we let that take precedence
155+
# over self.write
156+
try:
157+
sys.excepthook(typ, value, tb)
158+
except SystemExit:
159+
raise
160+
except BaseException as e:
161+
e.__context__ = None
162+
e = e.with_traceback(e.__traceback__.tb_next)
163+
print('Error in sys.excepthook:', file=sys.stderr)
164+
sys.__excepthook__(type(e), e, e.__traceback__)
165+
print(file=sys.stderr)
166+
print('Original exception was:', file=sys.stderr)
167+
sys.__excepthook__(typ, value, tb)
168+
169+
def _excepthook(self, typ, value, tb):
170+
# This method is being overwritten in
171+
# _pyrepl.console.InteractiveColoredConsole
172+
lines = traceback.format_exception(typ, value, tb)
173+
self.write(''.join(lines))
175174

176175
def write(self, data):
177176
"""Write a string.

Lib/test/test_pyrepl/test_pyrepl.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,7 @@ def test_dumb_terminal_exits_cleanly(self):
10201020
env.update({"TERM": "dumb"})
10211021
output, exit_code = self.run_repl("exit()\n", env=env)
10221022
self.assertEqual(exit_code, 0)
1023-
self.assertIn("warning: can\'t use pyrepl", output)
1023+
self.assertIn("warning: can't use pyrepl", output)
10241024
self.assertNotIn("Exception", output)
10251025
self.assertNotIn("Traceback", output)
10261026

@@ -1100,6 +1100,38 @@ def test_not_wiping_history_file(self):
11001100
self.assertIn("spam", output)
11011101
self.assertNotEqual(pathlib.Path(hfile.name).stat().st_size, 0)
11021102

1103+
@force_not_colorized
1104+
def test_proper_tracebacklimit(self):
1105+
env = os.environ.copy()
1106+
for set_tracebacklimit in [True, False]:
1107+
commands = ("import sys\n" +
1108+
("sys.tracebacklimit = 1\n" if set_tracebacklimit else "") +
1109+
"def x1(): 1/0\n\n"
1110+
"def x2(): x1()\n\n"
1111+
"def x3(): x2()\n\n"
1112+
"x3()\n"
1113+
"exit()\n")
1114+
1115+
for basic_repl in [True, False]:
1116+
if basic_repl:
1117+
env["PYTHON_BASIC_REPL"] = "1"
1118+
else:
1119+
env.pop("PYTHON_BASIC_REPL", None)
1120+
with self.subTest(set_tracebacklimit=set_tracebacklimit,
1121+
basic_repl=basic_repl):
1122+
output, exit_code = self.run_repl(commands, env=env)
1123+
if "can't use pyrepl" in output:
1124+
self.skipTest("pyrepl not available")
1125+
self.assertIn("in x1", output)
1126+
if set_tracebacklimit:
1127+
self.assertNotIn("in x2", output)
1128+
self.assertNotIn("in x3", output)
1129+
self.assertNotIn("in <module>", output)
1130+
else:
1131+
self.assertIn("in x2", output)
1132+
self.assertIn("in x3", output)
1133+
self.assertIn("in <module>", output)
1134+
11031135
def run_repl(
11041136
self,
11051137
repl_input: str | list[str],
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Make sure that the new :term:`REPL` interprets :data:`sys.tracebacklimit` in
2+
the same way that the classic REPL did.

0 commit comments

Comments
 (0)