Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions demo_truncate_boring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import stackprinter
from tests.source_boring import BoringClass

try:
boring = BoringClass()
boring.do_something_boring(for_num_of_frames=10, boring_sequences=3)
except Exception as e:
print("Oops!", e)
print(
stackprinter.format(
e, suppressed_paths=[r"source_boring\.py"], truncate_suppressed_frames=True, add_summary=False
)
)
243 changes: 139 additions & 104 deletions stackprinter/formatting.py
Original file line number Diff line number Diff line change
@@ -1,98 +1,132 @@
"""
Various convnenience methods to walk stacks and concatenate formatted frames
"""
import types

import traceback

import stackprinter.extraction as ex
import stackprinter.colorschemes as colorschemes
from stackprinter.utils import match, get_ansi_tpl
from stackprinter.frame_formatting import FrameFormatter, ColorfulFrameFormatter
import stackprinter.extraction as ex
from stackprinter.frame_formatting import ColorfulFrameFormatter, FrameFormatter
from stackprinter.utils import get_ansi_tpl, match


def get_formatter(style, **kwargs):
if style in ['plaintext', 'plain']:
if style in ["plaintext", "plain"]:
return FrameFormatter(**kwargs)
else:
return ColorfulFrameFormatter(style, **kwargs)


def format_summary(frames, style='plaintext', source_lines=1, reverse=False,
**kwargs):
def format_summary(frames, style="plaintext", source_lines=1, reverse=False, **kwargs):
"""
Render a list of frames with 1 line of source context, no variable values.

keyword args like stackprinter.format()
"""
min_src_lines = 0 if source_lines == 0 else 1
minimal_formatter = get_formatter(style=style,
source_lines=min_src_lines,
show_signature=False,
show_vals=False)
minimal_formatter = get_formatter(style=style, source_lines=min_src_lines, show_signature=False, show_vals=False)

frame_msgs = [minimal_formatter(frame) for frame in frames]
if reverse:
frame_msgs = reversed(frame_msgs)

return ''.join(frame_msgs)


def format_stack(frames, style='plaintext', source_lines=5,
show_signature=True, show_vals='like_source',
truncate_vals=500, line_wrap=60, reverse=False,
suppressed_paths=None, suppressed_vars=[]):
return "".join(frame_msgs)


def format_stack(
frames,
style="plaintext",
source_lines=5,
show_signature=True,
show_vals="like_source",
truncate_vals=500,
line_wrap=60,
reverse=False,
suppressed_paths=None,
suppressed_vars=[],
truncate_suppressed_frames=False, # TODO: check variable name!
):
"""
Render a list of frames (or FrameInfo tuples)

keyword args like stackprinter.format()
"""


min_src_lines = 0 if source_lines == 0 else 1

minimal_formatter = get_formatter(style=style,
source_lines=min_src_lines,
show_signature=False,
show_vals=False)

reduced_formatter = get_formatter(style=style,
source_lines=min_src_lines,
show_signature=show_signature,
show_vals=show_vals,
truncate_vals=truncate_vals,
line_wrap=line_wrap,
suppressed_paths=suppressed_paths,
suppressed_vars=suppressed_vars)

verbose_formatter = get_formatter(style=style,
source_lines=source_lines,
show_signature=show_signature,
show_vals=show_vals,
truncate_vals=truncate_vals,
line_wrap=line_wrap,
suppressed_paths=suppressed_paths,
suppressed_vars=suppressed_vars)
minimal_formatter = get_formatter(style=style, source_lines=min_src_lines, show_signature=False, show_vals=False)

reduced_formatter = get_formatter(
style=style,
source_lines=min_src_lines,
show_signature=show_signature,
show_vals=show_vals,
truncate_vals=truncate_vals,
line_wrap=line_wrap,
suppressed_paths=suppressed_paths,
suppressed_vars=suppressed_vars,
)

verbose_formatter = get_formatter(
style=style,
source_lines=source_lines,
show_signature=show_signature,
show_vals=show_vals,
truncate_vals=truncate_vals,
line_wrap=line_wrap,
suppressed_paths=suppressed_paths,
suppressed_vars=suppressed_vars,
)

frame_msgs = []
parent_is_boring = True
last_boring_frame_fi = None
boring_frames_count: int = 0
for frame in frames:
fi = ex.get_info(frame, suppressed_vars=suppressed_vars)
is_boring = match(fi.filename, suppressed_paths)
if is_boring:
if parent_is_boring:
formatter = minimal_formatter
if truncate_suppressed_frames and last_boring_frame_fi is not None:
# Already in a boring sequence and set to hide => skip frame
formatter = None
else:
formatter = reduced_formatter
# Not set to hide, or this is the first boring frame in sequence => display frame
if parent_is_boring:
formatter = minimal_formatter
elif truncate_suppressed_frames:
# Use minimal format for first boring frame
formatter = minimal_formatter
else:
formatter = reduced_formatter

# Remember last boring frame in case it is the last in the sequence of boring frames and
# should be displayed after the '... suppressed frames ...' message.
# Also increment count of boring frames in the sequence.
last_boring_frame_fi = fi
boring_frames_count += 1
else:
if last_boring_frame_fi:
# We are coming out of a boring sequence, print 'suppressed frames' line if sequence count > 2 (first and
# last boring frames are shown), then print last boring frame.
if boring_frames_count > 2:
frame_msgs.append(f"[--- {boring_frames_count - 2} suppressed frames truncated ---]\r\n")
frame_msgs.append(minimal_formatter(last_boring_frame_fi))

formatter = verbose_formatter

# Reset boring sequence tracking variables
last_boring_frame_fi = None
boring_frames_count = 0

parent_is_boring = is_boring
frame_msgs.append(formatter(fi))
if formatter is not None:
# formatter is None when we are hiding boring frames, else append normally
frame_msgs.append(formatter(fi))

if reverse:
frame_msgs = reversed(frame_msgs)

return ''.join(frame_msgs)
return "".join(frame_msgs)


def format_stack_from_frame(fr, add_summary=False, **kwargs):
Expand All @@ -112,9 +146,17 @@ def format_stack_from_frame(fr, add_summary=False, **kwargs):
return format_stack(stack, **kwargs)


def format_exc_info(etype, evalue, tb, style='plaintext', add_summary='auto',
reverse=False, suppressed_exceptions=[KeyboardInterrupt],
suppressed_vars=[], **kwargs):
def format_exc_info(
etype,
evalue,
tb,
style="plaintext",
add_summary="auto",
reverse=False,
suppressed_exceptions=[KeyboardInterrupt],
suppressed_vars=[],
**kwargs,
):
"""
Format an exception traceback, including the exception message

Expand All @@ -123,121 +165,114 @@ def format_exc_info(etype, evalue, tb, style='plaintext', add_summary='auto',
if etype is None:
etype = type(None)

if etype.__name__ == 'ExceptionGroup':
if etype.__name__ == "ExceptionGroup":
# Exception groups (new in py 3.11) aren't supported so far,
# but at least we fall back on the default message.
return ''.join(traceback.format_exception(etype, evalue, tb))
return "".join(traceback.format_exception(etype, evalue, tb))

msg = ''
msg = ""
try:
# First, recursively format any chained exceptions (exceptions
# during whose handling the given one happened).
# TODO: refactor this whole messy function to return a
# more... structured datastructure before assembling a string,
# so that e.g. a summary of the whole chain can be shown at
# the end.
context = getattr(evalue, '__context__', None)
cause = getattr(evalue, '__cause__', None)
suppress_context = getattr(evalue, '__suppress_context__', False)
context = getattr(evalue, "__context__", None)
cause = getattr(evalue, "__cause__", None)
suppress_context = getattr(evalue, "__suppress_context__", False)
if cause:
chained_exc = cause
chain_hint = ("\n\nThe above exception was the direct cause "
"of the following exception:\n\n")
chain_hint = "\n\nThe above exception was the direct cause " "of the following exception:\n\n"
elif context and not suppress_context:
chained_exc = context
chain_hint = ("\n\nWhile handling the above exception, "
"another exception occurred:\n\n")
chain_hint = "\n\nWhile handling the above exception, " "another exception occurred:\n\n"
else:
chained_exc = None

if chained_exc:
msg += format_exc_info(chained_exc.__class__,
chained_exc,
chained_exc.__traceback__,
style=style,
add_summary=add_summary,
reverse=reverse,
suppressed_vars=suppressed_vars,
**kwargs)

if style == 'plaintext':
msg += chain_hint
msg += format_exc_info(
chained_exc.__class__,
chained_exc,
chained_exc.__traceback__,
style=style,
add_summary=add_summary,
reverse=reverse,
suppressed_vars=suppressed_vars,
**kwargs,
)

if style == "plaintext":
msg += chain_hint
else:
sc = getattr(colorschemes, style)
clr = get_ansi_tpl(*sc.colors['exception_type'])
clr = get_ansi_tpl(*sc.colors["exception_type"])
msg += clr % chain_hint

# Now, actually do some formatting:
parts = []
if tb:
frameinfos = [ex.get_info(tb_, suppressed_vars=suppressed_vars)
for tb_ in _walk_traceback(tb)]
if (suppressed_exceptions and
issubclass(etype, tuple(suppressed_exceptions))):
summary = format_summary(frameinfos, style=style,
reverse=reverse, **kwargs)
frameinfos = [ex.get_info(tb_, suppressed_vars=suppressed_vars) for tb_ in _walk_traceback(tb)]
if suppressed_exceptions and issubclass(etype, tuple(suppressed_exceptions)):
summary = format_summary(frameinfos, style=style, reverse=reverse, **kwargs)
parts = [summary]
else:
whole_stack = format_stack(frameinfos, style=style,
reverse=reverse, **kwargs)
whole_stack = format_stack(frameinfos, style=style, reverse=reverse, **kwargs)
parts.append(whole_stack)

if add_summary == 'auto':
add_summary = whole_stack.count('\n') > 50
if add_summary == "auto":
add_summary = whole_stack.count("\n") > 50

if add_summary:
summary = format_summary(frameinfos, style=style,
reverse=reverse, **kwargs)
summary += '\n'
parts.append('---- (full traceback below) ----\n\n' if reverse else
'---- (full traceback above) ----\n')
summary = format_summary(frameinfos, style=style, reverse=reverse, **kwargs)
summary += "\n"
parts.append(
"---- (full traceback below) ----\n\n" if reverse else "---- (full traceback above) ----\n"
)
parts.append(summary)

exc = format_exception_message(etype, evalue, style=style)
parts.append('\n\n' if reverse else '')
parts.append("\n\n" if reverse else "")
parts.append(exc)

if reverse:
parts = reversed(parts)

msg += ''.join(parts)
msg += "".join(parts)

except Exception as exc:
import os
if 'PY_STACKPRINTER_DEBUG' in os.environ:
raise

our_tb = traceback.format_exception(exc.__class__,
exc,
exc.__traceback__,
chain=False)
where = getattr(exc, 'where', None)
context = " while formatting " + str(where) if where else ''
msg = 'Stackprinter failed%s:\n%s\n' % (context, ''.join(our_tb[-2:]))
msg += 'So here is your original traceback at least:\n\n'
msg += ''.join(traceback.format_exception(etype, evalue, tb))
if "PY_STACKPRINTER_DEBUG" in os.environ:
raise

our_tb = traceback.format_exception(exc.__class__, exc, exc.__traceback__, chain=False)
where = getattr(exc, "where", None)
context = " while formatting " + str(where) if where else ""
msg = "Stackprinter failed%s:\n%s\n" % (context, "".join(our_tb[-2:]))
msg += "So here is your original traceback at least:\n\n"
msg += "".join(traceback.format_exception(etype, evalue, tb))

return msg


def format_exception_message(etype, evalue, tb=None, style='plaintext'):
def format_exception_message(etype, evalue, tb=None, style="plaintext"):
type_str = etype.__name__
val_str = str(evalue)

if etype == SyntaxError and evalue.text:
val_str += '\n %s\n %s^' % (evalue.text.rstrip(), ' '*evalue.offset)
val_str += "\n %s\n %s^" % (evalue.text.rstrip(), " " * evalue.offset)

if val_str:
type_str += ": "

if style == 'plaintext':
if style == "plaintext":
return type_str + val_str
else:
sc = getattr(colorschemes, style)

clr_head = get_ansi_tpl(*sc.colors['exception_type'])
clr_msg = get_ansi_tpl(*sc.colors['exception_msg'])
clr_head = get_ansi_tpl(*sc.colors["exception_type"])
clr_msg = get_ansi_tpl(*sc.colors["exception_msg"])

return clr_head % type_str + clr_msg % val_str

Expand Down
Loading