]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-44569: Decouple frame formatting in traceback.py (GH-27038)
authorAmmar Askar <ammar@ammaraskar.com>
Fri, 16 Jul 2021 12:21:16 +0000 (08:21 -0400)
committerGitHub <noreply@github.com>
Fri, 16 Jul 2021 12:21:16 +0000 (13:21 +0100)
Doc/library/traceback.rst
Lib/test/test_traceback.py
Lib/traceback.py
Misc/NEWS.d/next/Library/2021-07-08-12-22-54.bpo-44569.KZ02v9.rst [new file with mode: 0644]

index be1f43ea953edbdc7759424b7a02ce4c1bcc5e25..83d5c8c6fcbd3202bccad11728f08c02f046f7e5 100644 (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
 -----------------------------
index 402f773814ec43ead22f4714a3a2d1df286f62c8..4742eb1d2309b4e5d19b52d41b4664b93f520559 100644 (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):
index 40d736af56dd71c4ae4b963e36e35ed18db85c55..ae5775d2f3bdae981e88b3d048ca3b3e3e800322 100644 (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()))
-
-                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(
diff --git a/Misc/NEWS.d/next/Library/2021-07-08-12-22-54.bpo-44569.KZ02v9.rst b/Misc/NEWS.d/next/Library/2021-07-08-12-22-54.bpo-44569.KZ02v9.rst
new file mode 100644 (file)
index 0000000..5f693b2
--- /dev/null
@@ -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.