File: stackwidget.py

package info (click to toggle)
python-pyqtgraph 0.14.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 8,168 kB
  • sloc: python: 54,831; makefile: 128; ansic: 40; sh: 2
file content (157 lines) | stat: -rw-r--r-- 5,310 bytes parent folder | download | duplicates (3)
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