Skip to content

Commit

Permalink
bpo-44569: Decouple frame formatting in traceback.py (GH-27038)
Browse files Browse the repository at this point in the history
  • Loading branch information
ammaraskar committed Jul 16, 2021
1 parent a283ef1 commit 8ce3008
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 34 deletions.
8 changes: 8 additions & 0 deletions Doc/library/traceback.rst
Expand Up @@ -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
-----------------------------
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_traceback.py
Expand Up @@ -1429,6 +1429,21 @@ def some_inner(k, v):
' 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):
Expand Down
78 changes: 44 additions & 34 deletions Lib/traceback.py
Expand Up @@ -449,6 +449,48 @@ def from_list(klass, a_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.
Expand Down Expand Up @@ -483,40 +525,8 @@ def format(self):
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()))

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))
result.append(self.format_frame(frame))

if count > _RECURSIVE_CUTOFF:
count -= _RECURSIVE_CUTOFF
result.append(
Expand Down
@@ -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.

0 comments on commit 8ce3008

Please sign in to comment.