Here's the code that illustrates the problem:
from PyQt4 import QtGui
app = QtGui.QApplication([])
dialog = QtGui.QDialog()
button = QtGui.QPushButton('I crash')
layout = QtGui.QHBoxLayout()
layout.addWidget(button)
dialog.setLayout(layout)
def crash(): raise Exception('Crash!')
button.clicked.connect(crash)
button.click()
print 'I should not happen'
When I run that, PyQt4 handles the error for me. My console displays a stack trace with 'Crash!' etc. in it, and I see 'I should not happen'.
This is not useful, because I've been handed a large application with many handlers, and I need to force all their errors up into my face (and into my - ahem - automated tests). Each time I run, errors escape my nets, and they would require excessive and useless try:except blocks, inside every handler, just to catch them all.
Put another way, I want good code to be very good, and bad code to be very bad. Not whitewashed.
Apologies if this is already asked, but when I e-search for it, I naturally get thousands of newbies asking basic error handling questions (or, worse, I get newbies asking how to turn OFF their wayward exceptions!;)
How do I override PyQt4's default error handling, so I can propagate or log errors myself? And please don't answer sys.excepthook, either - it catches the errors that PyQt4 doesn't catch.
This is not the answer, you silly website. Stop forcing us to fit into a preconceived notion of an idealized thread template.
The un-answer is to use my test framework setUp() to hook in an exception handler:
def setUp(self):
self.no_exceptions = True
def testExceptionHook(type, value, tback):
self.no_exceptions = False
sys.__excepthook__(type, value, tback)
sys.excepthook = testExceptionHook
Where that says "self.no_exceptions = False", I would much rather simply say self.fail(''). However, because Python's unit test library insists on throwing exceptions just to register test failures, and because PyQt insists on snarfing all exceptions, we have a deadlock.
To fit into unittest.TestCase's silly preconceived notion of an idealized test case, I have to instead set a variable, then detect it in the teardown:
def tearDown(self):
self.assertTrue(self.no_exceptions)
This is still not ideal, but at least it will force me to spend more time paying attention to the errors, instead of spending that time complaining about them on technical websites.
The root question: How to turn off PyQt's magic error handler? - remains unanswered...
I think the answer is that this isn't a 'feature' of PyQt
, but a consequence inherent to the design that lets signals/slots work (remember that the signal/slot communication is going through a c++ layer as well).
This is ugly, but does a bit of and end-run around your problem
from PyQt4 import QtGui
import time
app = QtGui.QApplication([])
class exception_munger(object):
def __init__(self):
self.flag = True
self.txt = ''
self.type = None
def indicate_fail(self,etype=None, txt=None):
self.flag = False
if txt is not None:
self.txt = txt
self.type = etype
def reset(self):
tmp_txt = self.txt
tmp_type = self.type
tmp_flag = self.flag
self.flag = True
self.txt = ''
self.type = None
return tmp_flag, tmp_type, tmp_txt
class e_manager():
def __init__(self):
self.old_hook = None
def __enter__(self):
em = exception_munger()
def my_hook(type, value, tback):
em.indicate_fail(type, value)
sys.__excepthook__(type, value, tback)
self.old_hook = sys.excepthook
sys.excepthook = my_hook
self.em = em
return self
def __exit__(self,*args,**kwargs):
sys.excepthook = self.old_hook
def mang_fac():
return e_manager()
def assert_dec(original_fun):
def new_fun(*args,**kwargs):
with mang_fac() as mf:
res = original_fun(*args, **kwargs)
flag, etype, txt = mf.em.reset()
if not flag:
raise etype(txt)
return res
return new_fun
@assert_dec
def my_test_fun():
dialog = QtGui.QDialog()
button = QtGui.QPushButton('I crash')
layout = QtGui.QHBoxLayout()
layout.addWidget(button)
dialog.setLayout(layout)
def crash():
time.sleep(1)
raise Exception('Crash!')
button.clicked.connect(crash)
button.click()
my_test_fun()
print 'should not happen'
This will not print 'should not happen' and gives you something to catch with your automated tests (with the correct exception type).
In [11]: Traceback (most recent call last):
File "/tmp/ipython2-3426rwB.py", line 68, in crash
Exception: Crash!
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
<ipython-input-11-6ef4090ab3de> in <module>()
----> 1 execfile(r'/tmp/ipython2-3426rwB.py') # PYTHON-MODE
/tmp/ipython2-3426rwB.py in <module>()
/tmp/ipython2-3426rwB.py in new_fun(*args, **kwargs)
Exception: Crash!
In [12]:
The stack trace is jacked up, but you can still read the first one that was printed out.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With