Skip to content

Commit 3830400

Browse files
committed
use StackSummary subclasses to filter frames
1 parent c90c591 commit 3830400

File tree

3 files changed

+51
-8
lines changed

3 files changed

+51
-8
lines changed

Lib/test/test_traceback.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,40 @@ def some_inner():
14431443
s.format(),
14441444
[f'{__file__}:{some_inner.__code__.co_firstlineno + 1}'])
14451445

1446+
def test_dropping_frames(self):
1447+
def f():
1448+
1/0
1449+
def g():
1450+
try:
1451+
f()
1452+
except:
1453+
return sys.exc_info()
1454+
exc_info = g()
1455+
1456+
class Skip_G(traceback.StackSummary):
1457+
def format_frame(self, frame):
1458+
if frame.name == 'g':
1459+
return None
1460+
return super().format_frame(frame)
1461+
1462+
def get_output(stack_summary_cls=None):
1463+
output = StringIO()
1464+
traceback.TracebackException(
1465+
*exc_info, stack_summary_cls=stack_summary_cls,
1466+
).print(file=output)
1467+
return output.getvalue().split('\n')
1468+
1469+
default = get_output()
1470+
skip_g = get_output(Skip_G)
1471+
1472+
for l in skip_g:
1473+
default.remove(l)
1474+
# Only the lines for g's frame should remain:
1475+
self.assertEqual(len(default), 3)
1476+
self.assertRegex(default[0], ', line [0-9]*, in g')
1477+
self.assertEqual(default[1], ' f()')
1478+
self.assertEqual(default[2], ' ^^^')
1479+
14461480

14471481
class TestTracebackException(unittest.TestCase):
14481482

Lib/traceback.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -507,25 +507,32 @@ def format(self):
507507
last_file = None
508508
last_line = None
509509
last_name = None
510+
last_line_displayed = False
510511
count = 0
511512
for frame in self:
512513
if (last_file is None or last_file != frame.filename or
513514
last_line is None or last_line != frame.lineno or
514515
last_name is None or last_name != frame.name):
515516
if count > _RECURSIVE_CUTOFF:
516517
count -= _RECURSIVE_CUTOFF
517-
result.append(
518-
f' [Previous line repeated {count} more '
519-
f'time{"s" if count > 1 else ""}]\n'
520-
)
518+
if last_line_displayed:
519+
result.append(
520+
f' [Previous line repeated {count} more '
521+
f'time{"s" if count > 1 else ""}]\n'
522+
)
521523
last_file = frame.filename
522524
last_line = frame.lineno
523525
last_name = frame.name
524526
count = 0
525527
count += 1
526528
if count > _RECURSIVE_CUTOFF:
527529
continue
528-
result.append(self.format_frame(frame))
530+
formatted_frame = self.format_frame(frame)
531+
if formatted_frame is not None:
532+
last_line_displayed = True
533+
result.append(formatted_frame)
534+
else:
535+
last_line_displayed = False
529536

530537
if count > _RECURSIVE_CUTOFF:
531538
count -= _RECURSIVE_CUTOFF
@@ -618,7 +625,7 @@ class TracebackException:
618625

619626
def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
620627
lookup_lines=True, capture_locals=False, compact=False,
621-
_seen=None):
628+
stack_summary_cls=None, _seen=None):
622629
# NB: we need to accept exc_traceback, exc_value, exc_traceback to
623630
# permit backwards compat with the existing API, otherwise we
624631
# need stub thunk objects just to glue it together.
@@ -628,8 +635,9 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
628635
_seen = set()
629636
_seen.add(id(exc_value))
630637

631-
# TODO: locals.
632-
self.stack = StackSummary._extract_from_extended_frame_gen(
638+
if stack_summary_cls is None:
639+
stack_summary_cls = StackSummary
640+
self.stack = stack_summary_cls._extract_from_extended_frame_gen(
633641
_walk_tb_with_full_positions(exc_traceback),
634642
limit=limit, lookup_lines=lookup_lines,
635643
capture_locals=capture_locals)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added the ``stack_summary_cls`` parameter to :class:`TracebackException`, to allow fine-grained control over the content of a formatted traceback. Added option to completely drop frames from the output by returning ``None`` from a :meth:`~StackSummary.format_frame` override.

0 commit comments

Comments
 (0)