bpo-44569: Decouple frame formatting in traceback.py (GH-27038)

This commit is contained in:
Ammar Askar 2021-07-16 08:21:16 -04:00 committed by GitHub
parent a283ef116b
commit 8ce3008585
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 33 deletions

View File

@ -353,6 +353,14 @@ capture data for later printing in a lightweight fashion.
.. versionchanged:: 3.6
Long sequences of repeated frames are now abbreviated.
.. method:: format_frame(frame)
Returns a string for printing one of the frames involved in the stack.
This method gets called for each frame object to be printed in the
:class:`StackSummary`.
.. versionadded:: 3.11
:class:`FrameSummary` Objects
-----------------------------

View File

@ -1429,6 +1429,21 @@ class TestStack(unittest.TestCase):
' v = 4\n' % (__file__, some_inner.__code__.co_firstlineno + 3)
], s.format())
def test_custom_format_frame(self):
class CustomStackSummary(traceback.StackSummary):
def format_frame(self, frame):
return f'{frame.filename}:{frame.lineno}'
def some_inner():
return CustomStackSummary.extract(
traceback.walk_stack(None), limit=1)
s = some_inner()
self.assertEqual(
s.format(),
[f'{__file__}:{some_inner.__code__.co_firstlineno + 1}'])
class TestTracebackException(unittest.TestCase):
def test_smoke(self):

View File

@ -449,6 +449,48 @@ class StackSummary(list):
result.append(FrameSummary(filename, lineno, name, line=line))
return result
def format_frame(self, frame):
"""Format the lines for a single frame.
Returns a string representing one frame involved in the stack. This
gets called for every frame to be printed in the stack summary.
"""
row = []
row.append(' File "{}", line {}, in {}\n'.format(
frame.filename, frame.lineno, frame.name))
if frame.line:
row.append(' {}\n'.format(frame.line.strip()))
stripped_characters = len(frame._original_line) - len(frame.line.lstrip())
if frame.end_lineno == frame.lineno and frame.end_colno != 0:
colno = _byte_offset_to_character_offset(frame._original_line, frame.colno)
end_colno = _byte_offset_to_character_offset(frame._original_line, frame.end_colno)
try:
anchors = _extract_caret_anchors_from_line_segment(
frame._original_line[colno - 1:end_colno - 1]
)
except Exception:
anchors = None
row.append(' ')
row.append(' ' * (colno - stripped_characters))
if anchors:
row.append(anchors.primary_char * (anchors.left_end_offset))
row.append(anchors.secondary_char * (anchors.right_start_offset - anchors.left_end_offset))
row.append(anchors.primary_char * (end_colno - colno - anchors.right_start_offset))
else:
row.append('^' * (end_colno - colno))
row.append('\n')
if frame.locals:
for name, value in sorted(frame.locals.items()):
row.append(' {name} = {value}\n'.format(name=name, value=value))
return ''.join(row)
def format(self):
"""Format the stack ready for printing.
@ -483,40 +525,8 @@ class StackSummary(list):
count += 1
if count > _RECURSIVE_CUTOFF:
continue
row = []
row.append(' File "{}", line {}, in {}\n'.format(
frame.filename, frame.lineno, frame.name))
if frame.line:
row.append(' {}\n'.format(frame.line.strip()))
result.append(self.format_frame(frame))
stripped_characters = len(frame._original_line) - len(frame.line.lstrip())
if frame.end_lineno == frame.lineno and frame.end_colno != 0:
colno = _byte_offset_to_character_offset(frame._original_line, frame.colno)
end_colno = _byte_offset_to_character_offset(frame._original_line, frame.end_colno)
try:
anchors = _extract_caret_anchors_from_line_segment(
frame._original_line[colno - 1:end_colno - 1]
)
except Exception:
anchors = None
row.append(' ')
row.append(' ' * (colno - stripped_characters))
if anchors:
row.append(anchors.primary_char * (anchors.left_end_offset))
row.append(anchors.secondary_char * (anchors.right_start_offset - anchors.left_end_offset))
row.append(anchors.primary_char * (end_colno - colno - anchors.right_start_offset))
else:
row.append('^' * (end_colno - colno))
row.append('\n')
if frame.locals:
for name, value in sorted(frame.locals.items()):
row.append(' {name} = {value}\n'.format(name=name, value=value))
result.append(''.join(row))
if count > _RECURSIVE_CUTOFF:
count -= _RECURSIVE_CUTOFF
result.append(

View File

@ -0,0 +1,3 @@
Added the :func:`StackSummary.format_frame` function in :mod:`traceback`.
This allows users to customize the way individual lines are formatted in
tracebacks without re-implementing logic to handle recursive tracebacks.