File: console_exception_inspection.py

package info (click to toggle)
python-pyqtgraph 0.13.7-5
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 8,068 kB
  • sloc: python: 54,043; makefile: 129; ansic: 40; sh: 2
file content (199 lines) | stat: -rw-r--r-- 5,521 bytes parent folder | download
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
"""
Using ConsoleWidget to interactively inspect exception backtraces


TODO
 - fix uncaught exceptions in threads (python 3.12)
 - allow using qtconsole
 - provide thread info for stacks
 - add thread browser?
 - add object browser?
    - clicking on a stack frame populates list of locals?
 - optional merged exception stacks

"""

import sys
import queue
import functools
import threading
import pyqtgraph as pg
import pyqtgraph.console
from pyqtgraph.Qt import QtWidgets
from pyqtgraph.debug import threadName


def raiseException():
    """Raise an exception
    """
    x = "inside raiseException()"
    raise Exception(f"Raised an exception {x} in {threadName()}")


def raiseNested():
    """Raise an exception while handling another
    """
    x = "inside raiseNested()"
    try:
        raiseException()
    except Exception:
        raise Exception(f"Raised during exception handling {x} in {threadName()}")


def raiseFrom():
    """Raise an exception from another
    """
    x = "inside raiseFrom()"
    try:
        raiseException()
    except Exception as exc:
        raise Exception(f"Raised-from during exception handling {x} in {threadName()}") from exc


def raiseCaughtException():
    """Raise and catch an exception
    """
    x = "inside raiseCaughtException()"
    try:
        raise Exception(f"Raised an exception {x} in {threadName()}")
    except Exception:
        print(f"Raised and caught exception {x} in {threadName()}  trace: {sys._getframe().f_trace}")


def captureStack():
    """Inspect the curent call stack
    """
    x = "inside captureStack()"
    global console
    console.setStack()
    return x


# Background thread for running functions
threadRunQueue = queue.Queue()
def threadRunner():
    global threadRunQueue
    # This is necessary to allow installing trace functions in the thread later on
    sys.settrace(lambda *args: None)
    while True:
        func, args = threadRunQueue.get()
        try:
            print(f"running {func} from thread, trace: {sys._getframe().f_trace}")
            func(*args)
        except Exception:
            sys.excepthook(*sys.exc_info())
thread = threading.Thread(target=threadRunner, name="background_thread", daemon=True)
thread.start()


# functions used to generate a stack a few items deep
def runInStack(func):
    x = "inside runInStack(func)"
    runInStack2(func)
    return x

def runInStack2(func):
    x = "inside runInStack2(func)"
    runInStack3(func)
    return x

def runInStack3(func):
    x = "inside runInStack3(func)"
    runInStack4(func)
    return x

def runInStack4(func):
    x = "inside runInStack4(func)"
    func()
    return x


class SignalEmitter(pg.QtCore.QObject):
    signal = pg.QtCore.Signal(object, object)
    def __init__(self, queued):
        pg.QtCore.QObject.__init__(self)
        if queued:
            self.signal.connect(self.run, pg.QtCore.Qt.ConnectionType.QueuedConnection)
        else:
            self.signal.connect(self.run)
    def run(self, func, args):
        func(*args)
signalEmitter = SignalEmitter(queued=False)
queuedSignalEmitter = SignalEmitter(queued=True)



def runFunc(func):
    if signalCheck.isChecked():
        if queuedSignalCheck.isChecked():
            func = functools.partial(queuedSignalEmitter.signal.emit, runInStack, (func,))
        else:
            func = functools.partial(signalEmitter.signal.emit, runInStack, (func,))
    
    if threadCheck.isChecked():
        threadRunQueue.put((runInStack, (func,)))
    else:
        runInStack(func)



funcs = [
    raiseException,
    raiseNested,
    raiseFrom,
    raiseCaughtException,
    captureStack,
]

app = pg.mkQApp()

win = pg.QtWidgets.QSplitter(pg.QtCore.Qt.Orientation.Horizontal)

ctrl = QtWidgets.QWidget()
ctrlLayout = QtWidgets.QVBoxLayout()
ctrl.setLayout(ctrlLayout)
win.addWidget(ctrl)

btns = []
for func in funcs:
    btn = QtWidgets.QPushButton(func.__doc__)
    btn.clicked.connect(functools.partial(runFunc, func))
    btns.append(btn)
    ctrlLayout.addWidget(btn)

threadCheck = QtWidgets.QCheckBox('Run in thread')
ctrlLayout.addWidget(threadCheck)

signalCheck = QtWidgets.QCheckBox('Run from Qt signal')
ctrlLayout.addWidget(signalCheck)

queuedSignalCheck = QtWidgets.QCheckBox('Use queued Qt signal')
ctrlLayout.addWidget(queuedSignalCheck)

ctrlLayout.addStretch()

console = pyqtgraph.console.ConsoleWidget(text="""
Use ConsoleWidget to interactively inspect exception tracebacks and call stacks!

- Enable "Show next exception" and the next unhandled exception will be displayed below.
- Click any of the buttons to the left to generate an exception.
- When an exception traceback is shown, you can select any of the stack frames and then run commands from that context,
  allowing you to inspect variables along the stack. (hint: most of the functions called by the buttons to the left 
  have a variable named "x" in their local scope)
- Note that this is not like a typical debugger--the program is not paused when an exception is caught; we simply keep
  a reference to the stack frames and continue on.
- By default, we only catch unhandled exceptions. If you need to inspect a handled exception (one that is caught by
  a try:except block), then uncheck the "Only handled exceptions" box. Note, however that this incurs a performance 
  penalty and will interfere with other debuggers.


""")
console.catchNextException()
win.addWidget(console)

win.resize(1400, 800)
win.setSizes([300, 1100])
win.show()

if __name__ == '__main__':
    pg.exec()