Use one single instance of QApplication in python unit test - python

How can I create one single instance of QApplication?
Background:
I am testing a couple of widgets implementing QWidget in unit tests. For this, I have to create an instance of QApplication. The second call to the constructor of QApplication results in an exception.
It works with following drawbacks:
The widget and the QApplication are created in setUpClass(cls), marked as #classmethod. For creation and maintenance of the tests, this is a pain cause every test has to deal with the same instance of the widget.
As soon as I have to execute multiple test cases, multiple QApplication instances are created and I'm facing a RuntimeError again...
My first working idea was to surround every call to QApplication() by a try except. But I am not happy with that...
I tried calling app.quit(), setting self.app = None and gc.collect(). None of them worked.
Technology facts:
Python 3.4
PySide
module unittest
Execution in PyCharm and console / script

Use the same QApplication instance for all unit tests in your test script.
To use the same QApplication instance, instantiate QApplication in the global scope of the unit test script.
Use a unique QWidget for each unit test.
To use a unique QWidget instance, instantiate QWidget in unittest.TestCase.setUp()
Here is a complete test script example to run from a console.
My environment is similar to yours except I am using:
PyQt5 instead of PySide
Jupyter QtConsole instead of PyCharm
#! /usr/bin/env python
import sys
import unittest
from PyQt5.QtWidgets import QApplication
"""All tests use the same single global instance of QApplication."""
from PyQt5.QtWidgets import QWidget
"""The tests individually instantiate the top-level window as a QWidget."""
# Create an application global accessible from all tests.
app= QApplication( sys.argv )
# Define setUp() code used in all tests.
class PyQt_TestFixture( unittest.TestCase ):
def create_application_window( self ):
w= QWidget()
return w
def setUp( self ):
self.window= self.create_application_window()
class TestPyQt( PyQt_TestFixture ):
"""Suite of tests. See test fixture for setUp()."""
def test_QWidget_setWindowTitle( self ):
"""Test that PyQt setWindowTitle() sets the title of the window."""
# Operate.
self.window.setWindowTitle( 'Test setWindowTitle' )
# Check.
self.assertEqual( self.window.windowTitle(), 'Test setWindowTitle' )
def test_eachTestUsesUniqueQWidgets( self ):
"""Test that the other test of PyQt setWindowTitle() is not affecting this test."""
# Check.
self.assertNotEqual( self.window.windowTitle(), 'Test setWindowTitle' )
"""The windowTitle() is an empty string '' if setWindowTitle() is not called."""
def test_QWidget_resize( self ):
"""Another example: test that PyQt resize() resizes the window."""
# Operate.
self.window.resize( 123, 456 )
# Check.
from PyQt5.QtCore import QSize
size= QSize( 123, 456 )
self.assertEqual( self.window.size(), size )
if __name__ == '__main__':
unittest.main()

Related

Issue when executing code snippet with "exec" and inheritance

I'm having some issues when trying to execute a string/file from within a QPlainTextEdit, it appears to be some sort of scoping issues. What happens is that when the code EXECUTABLE_STRING is run from the global scope, it works fine. However, when it is run from a local scope, such as through the AbstractPythonCodeWidget, it either can't find the object to do inheritance TypeError: super(type, obj): obj must be an instance or subtype of type or runs into a name error NameError: name 'Test' is not defined. Which oddly changes based on whether or not the exec(EXECUTABLE_STRING) line is commented/uncommented when run. Any help would be greatly appreciated.
import sys
from PyQt5.QtWidgets import QApplication, QPlainTextEdit
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor
app = QApplication(sys.argv)
EXECUTABLE_STRING = """
from PyQt5.QtWidgets import QLabel, QApplication
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor
class Test(QLabel):
def __init__(self, parent=None):
super(Test, self).__init__(parent)
self.setText("Test")
a = Test()
a.show()
a.move(QCursor.pos())
"""
class AbstractPythonCodeWidget(QPlainTextEdit):
def __init__(self, parent=None):
super(AbstractPythonCodeWidget, self).__init__(parent)
self.setPlainText(EXECUTABLE_STRING)
def keyPressEvent(self, event):
if event.modifiers() == Qt.ControlModifier:
if event.key() == Qt.Key_Return:
# this does not work
#exec(compile(self.toPlainText(), "script", "exec"), globals(), locals())
exec(self.toPlainText())
return QPlainTextEdit.keyPressEvent(self, event)
w = AbstractPythonCodeWidget()
w.show()
w.move(QCursor.pos())
w.resize(512, 512)
# this works when run here, but not when run on the keypress event
# exec(EXECUTABLE_STRING)
sys.exit(app.exec_())
First of all, running exec based on user input can be a security issue, but most importantly usually leads to fatal crash unless lots of precautions are taken, since you're using the same interpreter for both your program and the user code: basically, if the code of the user fails, your program fails, but that's not the only problem.
The reason for which your code doesn't run properly is actually a bit complex, and it's related to the scope of the class name, which becomes a bit complex when running exec along with super().[1]
An interesting aspect is that if you remove the arguments of super (and you should, since Python 3), the program won't raise any error.
But that won't be enough: a is a local variable, and it will be garbage collected as soon as exec is finished, and since the label is assigned to that variable, it will be destroyed along with it.
A possible solution would be to make the reference persistent, for example by assigning it to self (since self exists in the scope of the executed script). This is a working example of the EXECUTABLE_STRING:
from PyQt5.QtWidgets import QLabel
from PyQt5.QtGui import QCursor
class Test(QLabel):
def __init__(self, parent=None):
super().__init__(parent)
self.setText("Test")
self.a = Test()
self.a.show()
self.a.move(QCursor.pos())
As you can see, we don't need to import everything again anymore, we only import QLabel, since it wasn't imported in the main script, but everything else already exists in the scope of the script, including the current QApplication instance.
That said, all the above is only for knowledge purposes, as you should NOT use exec to run user code.
For instance, try to paste the following in the text edit, and run it:
self.document().setHtml('This should <b>NOT</b> happen!!!<br/><br/>Bye!')
self.setReadOnly(True)
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication
QTimer.singleShot(2000, QApplication.quit)
As you can see, not only the above code is able to change the input, but also take complete control of the whole application.
You could prevent that by calling a function that would basically limit the scope of the execution:
def runCode(code):
try:
exec(code)
except Exception as e:
return e
class AbstractPythonCodeWidget(QPlainTextEdit):
# ...
def keyPressEvent(self, event):
if event.modifiers() == Qt.ControlModifier:
if event.key() == Qt.Key_Return:
error = runCode(self.toPlainText())
if error:
QMessageBox.critical(self, 'Script crash!', str(error))
return QPlainTextEdit.keyPressEvent(self, event)
But that's just because no self is involved: you could still use w.a = Test() with the example above.
So, if you want to run user made scripts in your program, exec is probably not an acceptable solution, unless you take enough precautions.
If you don't need direct interaction between your program and the user script, a possibility could be to use the subprocess module, and run the script with another python interpreter instance.
[1] If anybody has a valid resource/answer that might shed some light on the topic, please comment.
Found a similar issue that goes into more depth about how the scope works with Globals/Locals in exec here:
globals and locals in python exec()
Don't want to copy/paste the entire thread, but the answer that worked for me in this post was:
d = dict(locals(), **globals())
exec (code, d, d)

QEventLoop in QAxWidget instance failed

I'm working on terminal based PyQt application using Windows OCX api.
And I'm having trouble over implement(calling exec) QEventLoop in QAxWidget instance.
If I call exec() in main QApplication main loop, everthing is fine.
But, calling exec() in instance of QAxWidget which is instantiated in main loop, it does not work as expected.
When exec() called(_on_event_connect in code), error message showed up like
"QEventLoop:exec: instance 0x18479d8 has already called exec()"
And on the moment of calling exit(), QEventLoop seems not running.
And the application is hang after this.
The below is simplified example of my code, It tries call QAxWidget method "dynamicCall" as soon as log-in process finished.
It consists of pyQt signal/slot mechanism, get event from API server.
Please understand that you can not execute this code because of it needs specific APIs installation and registered user-id.
But I hope you can see the points of problem.
main.py
import sys
from PyQt5.QtWidgets import QApplication
from api import OpenApi
class Main():
def __init__(self):
self.api = OpenApi()
self.api.comm_connect()
# self.api.req_basic_stock_info("035420") -> if I call here, works well
if __name__ == "__main__":
app = QApplication(sys.argv)
main = Main()
sys.exit(app.exec_())
api.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *
from PyQt5.QtCore import *
class OpenApi(QAxWidget):
def __init__(self):
super().__init__()
self.setControl("KHOPENAPI.KHOpenAPICtrl.1")
self.OnEventConnect.connect(self._on_event_connect)
self.OnReceiveTrData.connect(self._on_receive_tr_data)
self._qloop = QEventLoop()
def loop(self):
if not self._qloop.isRunning():
logger.info(f"-->exec")
self._qloop.exec()
else:
logger.info("exec skipped")
def unloop(self):
if self._qloop.isRunning():
logger.info("-->exit")
self._qloop.exit()
else:
logger.info(f"exit skipped")
def comm_connect(self):
self.dynamicCall("CommConnect()")
self.loop()
def comm_rq_data(self, rqname, trcode, next, screen_no):
self.dynamicCall("CommRqData(QString, QString, int, QString)", rqname, trcode, next, screen_no)
self.loop()
def _on_event_connect(self, err_code):
if err_code == 0:
logger.info("logged in")
else:
logger.info("log-in failed")
self.unloop()
# If I call this function here, QEventLoop fails
self.req_basic_stock_info("000660")
def _on_receive_tr_data(self, screen_no, rqname, trcode, record_name, next,
unused1, unused2, unused3, unused4):
logger.info(f"OnReceivTrData: {rqname}")
self.unloop()
def req_basic_stock_info(self, code):
# I want to call this function inside instance on certain condition.
self.set_input_value("item", code)
scrno = "2000"
self.comm_rq_data("ITEM_INFO_REQ", "opt10080", "0", scrno)
And output is like below.
12:42:53,54 test INFO -->exec
12:42:58,961 test INFO logged in
12:42:58,962 test INFO -->exit
12:42:58,977 test INFO -->exec
QEventLoop::exec: instance 0x35c34a0 has already called exec()
12:42:59,33 test INFO OnReceivTrData:ITEM_INFO_REQ
12:42:59,35 test INFO exit skipped
As eyllanesc told me, I changed functions related assign/deassign QEventLoop like below. And it works.
def loop(self):
self._qloop = QEventLoop()
self._qloop.exec()
def unloop(self):
try:
self._qloop.exit()
except AttributeError as e:
logger.error(e)
update
I'm afraid this issue has not been solved yet. It was successful when req_basic_stock_info function is called on _on_event_connect event, But I call it at another event function, it hang again without any error message. There is no difference in calling mechanism.
Calling the exit() function does not imply that the QEventLoop can be reused, therefore the error message is signaled. In this case it is better to use a new QEventLoop:
def unloop(self):
if self._qloop.isRunning():
logger.info("-->exit")
self._qloop.exit()
self._qloop = QEventLoop()
else:
logger.info(f"exit skipped")

Declaration of the custom Signals

In Qt, we can create custom signals by making them static variables. and then we use self.signame instead classname.signame.
So that creates an instance variable in the class.
I wanted to know the theory beyond this pattern.
here's some pseudo-code that I have tried that was recorded in most of the sources:
from PyQt5 import QtWidgets,QtCore
class test(QtWidgets.QApplication):
sig=QtCore.pyqtSignal([bool])
def __init__(self):
super().__init__([])
# self.sig=QtCore.pyqtSignal([bool]) #1
self.window=QtWidgets.QWidget()
self.sig.connect(lambda x=True:print('Hello World'))
self.bt=QtWidgets.QPushButton(self.window,text='test',clicked=self.sig)
self.window.setLayout(QtWidgets.QVBoxLayout())
self.window.layout().addWidget(self.bt)
self.window.show()
test().exec()
When i tred to access the signal test.sig instead of self.sig ,
from PyQt5 import QtWidgets,QtCore
class test(QtWidgets.QApplication):
def __init__(self):
super().__init__([])
self.sig=QtCore.pyqtSignal([bool])
self.window=QtWidgets.QWidget()
self.sig.connect(lambda x=True:print('Hello World'))
self.bt=QtWidgets.QPushButton(self.window,text='test',clicked=self.sig)
self.window.setLayout(QtWidgets.QVBoxLayout())
self.window.layout().addWidget(self.bt)
self.window.show()
test().exec()
i get this error:
test.sig.connect(lambda x=True:print('Hello World'))
AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect'
and when I tried to make the signal as an instance variable instead of the static variable I get that error (uncommenting the #1 in psudeo code)
I get the same error (as before).
May I know the reason behind this
Source: PyQt5
It should be noted that pyqtSignal is declared as an attribute of the class but it is not the same object that is used in the connection as indicated in the docs:
A signal (specifically an unbound signal) is a class attribute. When a
signal is referenced as an attribute of an instance of the class then
PyQt5 automatically binds the instance to the signal in order to
create a bound signal. This is the same mechanism that Python itself
uses to create bound methods from class functions.
A bound signal has connect(), disconnect() and emit() methods that
implement the associated functionality. It also has a signal attribute
that is the signature of the signal that would be returned by Qt’s
SIGNAL() macro.
In other words, sig = QtCore.pyqtSignal([bool]) is an unbound signal but self.sig is the bound signal and that can be verified with the following lines:
from PyQt5 import QtWidgets, QtCore
class test(QtWidgets.QApplication):
sig = QtCore.pyqtSignal([bool])
print(type(sig))
def __init__(self):
super().__init__([])
self.window = QtWidgets.QWidget()
print(type(self.sig))
self.sig.connect(lambda x=True: print("Hello World"))
self.bt = QtWidgets.QPushButton(self.window, text="test", clicked=self.sig)
self.window.setLayout(QtWidgets.QVBoxLayout())
self.window.layout().addWidget(self.bt)
self.window.show()
test().exec()
Output
<class 'PyQt5.QtCore.pyqtSignal'>
<class 'PyQt5.QtCore.pyqtBoundSignal'>
In conclusion, the attribute of the class "sig" is a pyqtSignal that does not have the connect method and that is used to construct the attribute "self.x" which is a pyqtBoundSignal that does have the connect method.

How can I monkey patch PyQT's QApplication.notify() to time events

In our PyQt application we want to time the duration of all Qt Events. Only in a special performance monitoring mode. Previously I subclassed QApplication and overrode the notify() method and that worked great. I wrote the data in chrome://tracing format and it was super helpful.
However when our application is run inside Jupyter there is a pre-existing QApplication instance. So I can't think of how to make it use my subclass.
Instead I tried monkey patching below, but my notify() is never called. I suspect notify() is a wrapped C++ method and it can't be monkey patched?
def monkey_patch_event_timing(app: QApplication):
original_notify = app.notify
def notify_with_timing(self, receiver, event):
timer_name = _get_timer_name(receiver, event)
# Time the event while we handle it.
with perf.perf_timer(timer_name, "qt_event"):
return original_notify(receiver, event)
bound_notify = MethodType(notify_with_timing, app)
# Check if we are already patched first.
if not hasattr(app, '_myproject_event_timing'):
print("Enabling Qt Event timing...")
app.notify = bound_notify
app._myproject_event_timing = True
Is there a way to monkey patch QApplication.notify or otherwise insert code somewhere that can time every Qt Event?
A possible solution is to remove the old QApplication with the help of sip and create a new one:
def monkey_patch_event_timing():
app = QApplication.instance()
if app is not None:
import sip
sip.delete(app)
class MyApplication(QApplication):
def notify(self, receiver, event):
ret = QApplication.notify(self, receiver, event)
print(ret, receiver, event)
return ret
app = MyApplication([])
return app

Simplest way for PyQT Threading

I have a GUI in PyQt with a function addImage(image_path). Easy to imagine, it is called when a new image should be added into a QListWidget. For the detection of new images in a folder, I use a threading.Thread with watchdog to detect file changes in the folder, and this thread then calls addImage directly.
This yields the warning that QPixmap shouldn't be called outside the gui thread, for reasons of thread safety.
What is the best and most simple way to make this threadsafe? QThread? Signal / Slot? QMetaObject.invokeMethod? I only need to pass a string from the thread to addImage.
You should use the built in QThread provided by Qt. You can place your file monitoring code inside a worker class that inherits from QObject so that it can use the Qt Signal/Slot system to pass messages between threads.
class FileMonitor(QObject):
image_signal = QtCore.pyqtSignal(str)
#QtCore.pyqtSlot()
def monitor_images(self):
# I'm guessing this is an infinite while loop that monitors files
while True:
if file_has_changed:
self.image_signal.emit('/path/to/image/file.jpg')
class MyWidget(QtGui.QWidget):
def __init__(self, ...)
...
self.file_monitor = FileMonitor()
self.thread = QtCore.QThread(self)
self.file_monitor.image_signal.connect(self.image_callback)
self.file_monitor.moveToThread(self.thread)
self.thread.started.connect(self.file_monitor.monitor_images)
self.thread.start()
#QtCore.pyqtSlot(str)
def image_callback(self, filepath):
pixmap = QtGui.QPixmap(filepath)
...
I believe the best approach is using the signal/slot mechanism. Here is an example. (Note: see the EDIT below that points out a possible weakness in my approach).
from PyQt4 import QtGui
from PyQt4 import QtCore
# Create the class 'Communicate'. The instance
# from this class shall be used later on for the
# signal/slot mechanism.
class Communicate(QtCore.QObject):
myGUI_signal = QtCore.pyqtSignal(str)
''' End class '''
# Define the function 'myThread'. This function is the so-called
# 'target function' when you create and start your new Thread.
# In other words, this is the function that will run in your new thread.
# 'myThread' expects one argument: the callback function name. That should
# be a function inside your GUI.
def myThread(callbackFunc):
# Setup the signal-slot mechanism.
mySrc = Communicate()
mySrc.myGUI_signal.connect(callbackFunc)
# Endless loop. You typically want the thread
# to run forever.
while(True):
# Do something useful here.
msgForGui = 'This is a message to send to the GUI'
mySrc.myGUI_signal.emit(msgForGui)
# So now the 'callbackFunc' is called, and is fed with 'msgForGui'
# as parameter. That is what you want. You just sent a message to
# your GUI application! - Note: I suppose here that 'callbackFunc'
# is one of the functions in your GUI.
# This procedure is thread safe.
''' End while '''
''' End myThread '''
In your GUI application code, you should create the new Thread, give it the right callback function, and make it run.
from PyQt4 import QtGui
from PyQt4 import QtCore
import sys
import os
# This is the main window from my GUI
class CustomMainWindow(QtGui.QMainWindow):
def __init__(self):
super(CustomMainWindow, self).__init__()
self.setGeometry(300, 300, 2500, 1500)
self.setWindowTitle("my first window")
# ...
self.startTheThread()
''''''
def theCallbackFunc(self, msg):
print('the thread has sent this message to the GUI:')
print(msg)
print('---------')
''''''
def startTheThread(self):
# Create the new thread. The target function is 'myThread'. The
# function we created in the beginning.
t = threading.Thread(name = 'myThread', target = myThread, args = (self.theCallbackFunc))
t.start()
''''''
''' End CustomMainWindow '''
# This is the startup code.
if __name__== '__main__':
app = QtGui.QApplication(sys.argv)
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Plastique'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
''' End Main '''
EDIT
Mr. three_pineapples and Mr. Brendan Abel pointed out a weakness in my approach. Indeed, the approach works fine for this particular case, because you generate / emit the signal directly. When you deal with built-in Qt signals on buttons and widgets, you should take another approach (as specified in the answer of Mr. Brendan Abel).
Mr. three_pineapples adviced me to start a new topic in StackOverflow to make a comparison between the several approaches of thread-safe communication with a GUI. I will dig into the matter, and do that tomorrow :-)

Categories

Resources