1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
import sys, traceback
from ..Qt import QtWidgets, QtGui
class StackWidget(QtWidgets.QTreeWidget):
def __init__(self, parent=None):
QtWidgets.QTreeWidget.__init__(self, parent)
self.setAlternatingRowColors(True)
self.setHeaderHidden(True)
def selectedFrame(self):
"""Return the currently selected stack frame (or None if there is no selection)
"""
sel = self.selectedItems()
if len(sel) == 0:
return None
else:
return sel[0].frame
def clear(self):
QtWidgets.QTreeWidget.clear(self)
self.frames = []
def setException(self, exc=None, lastFrame=None):
"""Display an exception chain with its tracebacks and call stack.
"""
if exc is None:
exc = sys.exc_info()[1]
self.clear()
exceptions = exceptionChain(exc)
for ex, cause in exceptions:
stackFrames, tbFrames = stacksFromTraceback(ex.__traceback__, lastFrame=lastFrame)
catchMsg = textItem("Exception caught here")
excStr = ''.join(traceback.format_exception_only(type(ex), ex)).strip()
items = makeItemTree(stackFrames + [catchMsg] + tbFrames, excStr)
self.addTopLevelItem(items[0])
if cause is not None:
if cause == 'cause':
causeItem = textItem("The above exception was the direct cause of the following exception:")
elif cause == 'context':
causeItem = textItem("During handling of the above exception, another exception occurred:")
self.addTopLevelItem(causeItem)
items[0].setExpanded(True)
def setStack(self, frame=None, expand=True, lastFrame=None):
"""Display a call stack and exception traceback.
This allows the user to probe the contents of any frame in the given stack.
*frame* may either be a Frame instance or None, in which case the current
frame is retrieved from ``sys._getframe()``.
If *tb* is provided then the frames in the traceback will be appended to
the end of the stack list. If *tb* is None, then sys.exc_info() will
be checked instead.
"""
if frame is None:
frame = sys._getframe().f_back
self.clear()
stack = stackFromFrame(frame, lastFrame=lastFrame)
items = makeItemTree(stack, "Call stack")
self.addTopLevelItem(items[0])
if expand:
items[0].setExpanded(True)
def stackFromFrame(frame, lastFrame=None):
"""Return (text, stack_frame) for the entire stack ending at *frame*
If *lastFrame* is given and present in the stack, then the stack is truncated
at that frame.
"""
lines = traceback.format_stack(frame)
frames = []
while frame is not None:
frames.insert(0, frame)
frame = frame.f_back
if lastFrame is not None and lastFrame in frames:
frames = frames[:frames.index(lastFrame)+1]
return list(zip(lines[:len(frames)], frames))
def stacksFromTraceback(tb, lastFrame=None):
"""Return (text, stack_frame) for a traceback and the stack preceding it
If *lastFrame* is given and present in the stack, then the stack is truncated
at that frame.
"""
# get stack before tb
stack = stackFromFrame(tb.tb_frame.f_back if tb is not None else lastFrame)
if tb is None:
return stack, []
# walk to last frame of traceback
lines = traceback.format_tb(tb)
frames = []
while True:
frames.append(tb.tb_frame)
if tb.tb_next is None or tb.tb_frame is lastFrame:
break
tb = tb.tb_next
return stack, list(zip(lines[:len(frames)], frames))
def makeItemTree(stack, title):
topItem = QtWidgets.QTreeWidgetItem([title])
topItem.frame = None
font = topItem.font(0)
font.setWeight(font.Weight.Bold)
topItem.setFont(0, font)
items = [topItem]
for entry in stack:
if isinstance(entry, QtWidgets.QTreeWidgetItem):
item = entry
else:
text, frame = entry
item = QtWidgets.QTreeWidgetItem([text.rstrip()])
item.frame = frame
topItem.addChild(item)
items.append(item)
return items
def exceptionChain(exc):
"""Return a list of (exception, 'cause'|'context') pairs for exceptions
leading up to *exc*
"""
exceptions = [(exc, None)]
while True:
# walk through chained exceptions
if exc.__cause__ is not None:
exc = exc.__cause__
exceptions.insert(0, (exc, 'cause'))
elif exc.__context__ is not None and exc.__suppress_context__ is False:
exc = exc.__context__
exceptions.insert(0, (exc, 'context'))
else:
break
return exceptions
def textItem(text):
"""Return a tree item with no associated stack frame and a darker background color
"""
item = QtWidgets.QTreeWidgetItem([text])
item.frame = None
item.setBackground(0, QtGui.QBrush(QtGui.QColor(220, 220, 220)))
item.setForeground(0, QtGui.QBrush(QtGui.QColor(0, 0, 0)))
item.setChildIndicatorPolicy(item.ChildIndicatorPolicy.DontShowIndicator)
return item
|