8000 gh-122478: Remove internal frames from tracebacks in REPL (GH-122528) · python/cpython@e73e7a7 · GitHub
[go: up one dir, main page]

Skip to content

Commit e73e7a7

Browse files
gh-122478: Remove internal frames from tracebacks in REPL (GH-122528)
Frames of methods in code and codeop modules was show with non-default sys.excepthook. Save correct tracebacks in sys.last_traceback and update __traceback__ attribute of sys.last_value and sys.last_exc.
1 parent 42d9bec commit e73e7a7

File tree

3 files changed

+161
-54
lines changed
  • Misc/NEWS.d/next/Library
  • 3 files changed

    +161
    -54
    lines changed

    Lib/code.py

    Lines changed: 41 additions & 46 deletions
    Original file line numberDiff line numberDiff line change
    @@ -107,29 +107,21 @@ def showsyntaxerror(self, filename=None, **kwargs):
    107107
    108108
    """
    109109
    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-
    if sys.excepthook is sys.__excepthook__:
    127-
    lines = traceback.format_exception_only(type, value, colorize=colorize)
    128-
    self.write(''.join(lines))
    129-
    else:
    130-
    # If someone has set sys.excepthook, we let that take precedence
    131-
    # over self.write
    132-
    self._call_excepthook(type, value, tb)
    110+
    try:
    111+
    typ, value, tb = sys.exc_info()
    112+
    if filename and typ is SyntaxError:
    113+
    # Work hard to stuff the correct filename in the exception
    114+
    try:
    115+
    msg, (dummy_filename, lineno, offset, line) = value.args
    116+
    except ValueError:
    117+
    # Not the format we expect; leave it alone
    118+
    pass
    119+
    else:
    120+
    # Stuff in the right filename
    121+
    value = SyntaxError(msg, (filename, lineno, offset, line))
    122+
    self._showtraceback(typ, value, None, colorize)
    123+
    finally:
    124+
    typ = value = tb = None
    133125

    134126
    def showtraceback(self, **kwargs):
    135127
    """Display the exception that just occurred.
    @@ -140,32 +132,35 @@ def showtraceback(self, **kwargs):
    140132
    141133
    """
    142134
    colorize = kwargs.pop('colorize', False)
    143-
    sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
    144-
    sys.last_traceback = last_tb
    145-
    sys.last_exc = ei[1]
    146135
    try:
    147-
    if sys.excepthook is sys.__excepthook__:
    148-
    lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next, colorize=colorize)
    149-
    self.write(''.join(lines))
    150-
    else:
    151-
    # If someone has set sys.excepthook, we let that take precedence
    152-
    # over self.write
    153-
    self._call_excepthook(ei[0], ei[1], last_tb)
    136+
    typ, value, tb = sys.exc_info()
    137+
    self._showtraceback(typ, value, tb.tb_next, colorize)
    154138
    finally:
    155-
    last_tb = ei = None
    139+
    typ = value = tb = None
    156140

    157-
    def _call_excepthook(self, typ, value, tb):
    158-
    try:
    159-
    sys.excepthook(typ, value, tb)
    160-
    except SystemExit:
    161-
    raise
    162-
    except BaseException as e:
    163-
    e.__context__ = None
    164-
    print('Error in sys.excepthook:', file=sys.stderr)
    165-
    sys.__excepthook__(type(e), e, e.__traceback__.tb_next)
    166-
    print(file=sys.stderr)
    167-
    print('Original exception was:', file=sys.stderr)
    168-
    sys.__excepthook__(typ, value, tb)
    141+
    def _showtraceback(self, typ, value, tb, colorize):
    142+
    sys.last_type = typ
    143+
    sys.last_traceback = tb
    144+
    sys.last_exc = sys.last_value = value = value.with_traceback(tb)
    145+
    if sys.excepthook is sys.__excepthook__:
    146+
    lines = traceback.format_exception(typ, value, tb,
    147+
    colorize=colorize)
    148+
    self.write(''.join(lines))
    149+
    else:
    150+
    # If someone has set sys.excepthook, we let that take precedence
    151+
    # over self.write
    152+
    try:
    153+
    sys.excepthook(typ, value, tb)
    154+
    except SystemExit:
    155+
    raise
    156+
    except BaseException as e:
    157+
    e.__context__ = None
    158+
    e = e.with_traceback(e.__traceback__.tb_next)
    159+
    print('Error in sys.excepthook:', file=sys.stderr)
    160+
    sys.__excepthook__(type(e), e, e.__traceback__)
    161+
    print(file=sys.stderr)
    162+
    print('Original exception was:', file=sys.stderr)
    163+
    sys.__excepthook__(typ, value, tb)
    169164

    170165
    def write(self, data):
    171166
    """Write a string.

    Lib/test/test_code_module.py

    Lines changed: 117 additions & 8 deletions
    Original file line numberDiff line numberDiff line change
    @@ -1,5 +1,6 @@
    11
    "Test InteractiveConsole and InteractiveInterpreter from code module"
    22
    import sys
    3+
    import traceback
    34
    import unittest
    45
    from textwrap import dedent
    56
    from contextlib import ExitStack
    @@ -30,6 +31,7 @@ def mock_sys(self):
    3031

    3132

    3233
    class TestInteractiveConsole(unittest.TestCase, MockSys):
    34+
    maxDiff = None
    3335

    3436
    def setUp(self):
    3537
    self.console = code.InteractiveConsole()
    @@ -61,21 +63,118 @@ def test_console_stderr(self):
    6163
    raise AssertionError("no console stdout")
    6264

    6365
    def test_syntax_error(self):
    64-
    self.infunc.side_effect = ["undefined", EOFError('Finished')]
    66+
    self.infunc.side_effect = ["def f():",
    67+
    " x = ?",
    68+
    "",
    69+
    EOFError('Finished')]
    6570
    self.console.interact()
    66-
    for call in self.stderr.method_calls:
    67-
    if 'NameError' in ''.join(call[1]):
    68-
    break
    69-
    else:
    70-
    raise AssertionError("No syntax error from console")
    71+
    output = ''.join(''.join(call[1]) for call in self.stderr.method_calls)
    72+
    output = output[output.index('(InteractiveConsole)'):]
    73+
    output = output[:output.index('\nnow exiting')]
    74+
    self.assertEqual(output.splitlines()[1:], [
    75+
    ' File "<console>", line 2',
    76+
    ' x = ?',
    77+
    ' ^',
    78+
    'SyntaxError: invalid syntax'])
    79+
    self.assertIs(self.sysmod.last_type, SyntaxError)
    80+
    self.assertIs(type(self.sysmod.last_value), SyntaxError)
    81+
    self.assertIsNone(self.sysmod.last_traceback)
    82+
    self.assertIsNone(self.sysmod.last_value.__traceback__)
    83+
    self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
    84+
    85+
    def test_indentation_error(self):
    86+
    self.infunc.side_effect = [" 1", EOFError('Finished')]
    87+
    self.console.interact()
    88+
    output = ''.join(''.join(call[1]) for call in self.stderr.method_calls)
    89+
    output = output[output.index('(InteractiveConsole)'):]
    90+
    output = output[:output.index('\nnow exiting')]
    91+
    self.assertEqual(output.splitlines()[1:], [
    92+
    ' File "<console>", line 1',
    93+
    ' 1',
    94+
    'IndentationError: unexpected indent'])
    95+
    self.assertIs(self.sysmod.last_type, IndentationError)
    96+
    self.assertIs(type(self.sysmod.last_value), IndentationError)
    97+
    self.assertIsNone(self.sysmod.last_traceback)
    98+
    self.assertIsNone(self.sysmod.last_value.__traceback__)
    99+
    self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
    100+
    101+
    def test_unicode_error(self):
    102+
    self.infunc.side_effect = ["'\ud800'", EOFError('Finished')]
    103+
    self.console.interact()
    104+
    output = ''.join(''.join(call[1]) for call in self.stderr.method_calls)
    105+
    output = output[output.index('(InteractiveConsole)'):]
    106+
    output = output[output.index('\n') + 1:]
    107+
    self.assertTrue(output.startswith('UnicodeEncodeError: '), output)
    108+
    self.assertIs(self.sysmod.last_type, UnicodeEncodeError)
    109+
    self.assertIs(type(self.sysmod.last_value), UnicodeEncodeError)
    110+
    self.assertIsNone(self.sysmod.last_traceback)
    111+
    self.assertIsNone(self.sysmod.last_value.__traceback__)
    112+
    self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
    71113

    72114
    def test_sysexcepthook(self):
    73-
    self.infunc.side_effect = ["raise ValueError('')",
    115+
    self.infunc.side_effect = ["def f():",
    116+
    " raise ValueError('BOOM!')",
    117+
    6D38 "",
    118+
    "f()",
    74119
    EOFError('Finished')]
    75120
    hook = mock.Mock()
    76121
    self.sysmod.excepthook = hook
    77122
    self.console.interact()
    78-
    self.assertTrue(hook.called)
    123+
    hook.assert_called()
    124+
    hook.assert_called_with(self.sysmod.last_type,
    125+
    self.sysmod.last_value,
    126+
    self.sysmod.last_traceback)
    127+
    self.assertIs(self.sysmod.last_type, ValueError)
    128+
    self.assertIs(type(self.sysmod.last_value), ValueError)
    129+
    self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__)
    130+
    self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
    131+
    self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [
    132+
    'Traceback (most recent call last):\n',
    133+
    ' File "<console>", line 1, in <module>\n',
    134+
    ' File "<console>", line 2, in f\n',
    135+
    'ValueError: BOOM!\n'])
    136+
    137+
    def test_sysexcepthook_syntax_error(self):
    138+
    self.infunc.side_effect = ["def f():",
    139+
    " x = ?",
    140+
    "",
    141+
    EOFError('Finished')]
    142+
    hook = mock.Mock()
    143+
    self.sysmod.excepthook = hook
    144+
    self.console.interact()
    145+
    hook.assert_called()
    146+
    hook.assert_called_with(self.sysmod.last_type,
    147+
    self.sysmod.last_value,
    148+
    self.sysmod.last_traceback)
    149+
    self.assertIs(self.sysmod.last_type, SyntaxError)
    150+
    self.assertIs(type(self.sysmod.last_value), SyntaxError)
    151+
    self.assertIsNone(self.sysmod.last_traceback)
    152+
    self.assertIsNone(self.sysmod.last_value.__traceback__)
    153+
    self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
    154+
    self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [
    155+
    ' File "<console>", line 2\n',
    156+
    ' x = ?\n',
    157+
    ' ^\n',
    158+
    'SyntaxError: invalid syntax\n'])
    159+
    160+
    def test_sysexcepthook_indentation_error(self):
    161+
    self.infunc.side_effect = [" 1", EOFError('Finished')]
    162+
    hook = mock.Mock()
    163+
    self.sysmod.excepthook = hook
    164+
    self.console.interact()
    165+
    hook.assert_called()
    166+
    hook.assert_called_with(self.sysmod.last_type,
    167+
    self.sysmod.last_value,
    168+
    self.sysmod.last_traceback)
    169+
    self.assertIs(self.sysmod.last_type, IndentationError)
    170+
    self.assertIs(type(self.sysmod.last_value), IndentationError)
    171+
    self.assertIsNone(self.sysmod.last_traceback)
    172+
    self.assertIsNone(self.sysmod.last_value.__traceback__)
    173+
    self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
    174+
    self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [
    175+
    ' File "<console>", line 1\n',
    176+
    ' 1\n',
    177+
    'IndentationError: unexpected indent\n'])
    79178

    80179
    def test_sysexcepthook_crashing_doesnt_close_repl(self):
    81180
    self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')]
    @@ -167,6 +266,11 @@ def test_cause_tb(self):
    167266
    ValueError
    168267
    """)
    169268
    self.assertIn(expected, output)
    269+
    self.assertIs(self.sysmod.last_type, ValueError)
    270+
    self.assertIs(type(self.sysmod.last_value), ValueError)
    271+
    self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__)
    272+
    self.assertIsNotNone(self.sysmod.last_traceback)
    273+
    self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
    170274

    171275
    def test_context_tb(self):
    172276
    self.infunc.side_effect = ["try: ham\nexcept: eggs\n",
    @@ -185,6 +289,11 @@ def test_context_tb(self):
    185289
    NameError: name 'eggs' is not defined
    186290
    """)
    187291
    self.assertIn(expected, output)
    292+
    self.assertIs(self.sysmod.last_type, NameError)
    293+
    self.assertIs(type(self.sysmod.last_value), NameError)
    294+
    self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__)
    295+
    self.assertIsNotNone(self.sysmod.last_traceback)
    296+
    self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
    188297

    189298

    190299
    class TestInteractiveConsoleLocalExit(unittest.TestCase, MockSys):
    Lines changed: 3 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,3 @@
    1+
    Remove internal frames from tracebacks shown in
    2+
    :class:`code.InteractiveInterpreter` with non-default :func:`sys.excepthook`.
    3+
    Save correct tracebacks in :attr:`sys.last_traceback` and update ``__traceback__`` attribute of :attr:`sys.last_value` and :attr:`sys.last_exc`.

    0 commit comments

    Comments
     (0)
    0