How to launch an event loop inside a pyside unittest? - python

I'm trying to make unit tests on classes that involve signals and QTimers. (see the answer to this question for an idealization of one of the classes). I'm having a hard time trying to understand where to put the app.exec_() call without freezing everything and executing the tests with the signals being emitted. Just like in the question I cite, I think the problem here is that I need an event loop but putting in the setUpClass definitely doesn't work. This is the smallest piece of code I made to reproduce my problem, which in reality is with a larger code base.
testSignals.py:
import time
from PySide import QtCore
class TestSignals(QtCore.QObject):
def __init__(self):
self.timer = QtCore.QTimer()
self.myTime = ''
self.timer.timeout.connect(self.updateTime)
self.timer.start(100)
def getTime(self):
return self.myTime
def updateTime(self):
self.myTime = time.strftime('%H:%M:%S')
def stop(self):
self.timer.stop()
unitTesting.py:
import unittest
from testSignals import TestSignals
from PySide import QtGui, QtCore
from testSignals import TestSignals
import sys
class test(unittest.TestCase):
#classmethod
def setUpClass(cls):
app = QtGui.QApplication(sys.argv)
assert id(app) == id(QtGui.qApp) and id(app) == id(QtCore.QCoreApplication.instance())
cls.test = TestSignals()
# app.exec_() # uncommenting this only causes the code to freeze
def testStarted(self):
mytime = self.test.getTime()
self.assertNotEqual(mytime,'','Time is still empty')
if __name__ == '__main__':
unittest.main()
and the result is a failure of the only test because "mytime" is still empty. Just for clarity, the class does work:
class testConnection():
def receiveMe(self):
print(self.test.getTime())
# print(self.test.getData())
# signal is emmited when data is changed
def __init__(self):
self.test = TestSignals()
self.test.timer.timeout.connect(self.receiveMe)
# self.test.start()
app = QtCore.QCoreApplication([])
test = testConnection()
timer = QtCore.QTimer()
timer.singleShot(4000,app.exit)
app.exec_()
produces:
>>>$ python3 testConection.py
14:50:21
14:50:22
14:50:23
14:50:24

Related

When using a QThread, how do i pass data to the function that is called with .connect()?

I am using a QThread and, when my worker class is dont with the big work, i want to call a function that does changes to my UI (in this case with .finished.connect(), but i dont fully understand how the .connect() step works and how i can pass variables to the function called.
As an example: A button that, when pressed, opens a Thread in which it counts up, and changes the button text to the number its on. When the counter is finished, it prints something. Very simple and it works just fine.
main.py:
from PyQt5 import QtWidgets
import sys
import worker
from PyQt5.QtCore import QThread
from start import test
class App(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent)
self.init_ui()
self.initiate_buttons()
self.obj = worker.Worker()
self.thread = QThread()
self.obj.intReady.connect(self.onIntReady)
self.obj.moveToThread(self.thread)
self.obj.finished.connect(self.thread.quit)
self.obj.finished.connect(test)
self.thread.started.connect(self.obj.procCounter)
def init_ui(self):
self.pushButton = QtWidgets.QPushButton(self)
def onIntReady(self, i):
self.pushButton.setText("{}".format(i))
def initiate_buttons(self):
print("clicked")
self.pushButton.clicked.connect(lambda: self.thread.start())
def run():
application = QtWidgets.QApplication(sys.argv)
main_app = App()
main_app.show()
application.exec()
if __name__ == '__main__':
run()
worker.py:
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
import time
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int)
#pyqtSlot()
def procCounter(self):
for i in range(1, 5):
time.sleep(1)
self.intReady.emit(i)
self.finished.emit()
start.py:
def test(x="bar"):
print("test"+x)
So the thing i am talking about is start.py - i can put the function in its own module just fine and it worked. But what if i want to pass a parameter to it? I tried the obvious attempt, changing self.obj.finished.connect(test) to self.obj.finished.connect(test(x="foo")) - but that doesnt work. But i dont understand why it does not work, and how else i am supposed to do this. In the end, i want the module to do changes to my UI, just like onIntReady. I found this and this but did not understand how i can apply it.

QThread: Threading with GUI python

I am making mini chat application to improve my socket and GUI skills in Python. But my QThread is dumping every time when I'm launching application.
I need to run Thread with GUI
Here it is client.py
import socket
import sys
from colorama import Fore, Back, Style
from os import system, name
import sys
from chat import Ui_Form as Ui_Form
from login import Ui_Form as Ui_Form1
from PyQt5 import QtCore, QtGui, QtWidgets
from threading import Thread
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
app = QtWidgets.QApplication(sys.argv)
Form_login = QtWidgets.QWidget()
ui_login = Ui_Form1()
ui_login.setupUi(Form_login)
Form_login.show()
Form_chat = QtWidgets.QWidget()
ui_chat = Ui_Form()
ui_chat.setupUi(Form_chat)
history = ''
class listen_thread(QtCore.QObject):
running = False
listen_var = QtCore.pyqtSignal(str)
def run():
print('checkpoint')
while True:
listen_var.emit('message')
QtCore.QThread.msleep(1000)
def connect_pressed():
username = ui_login.lineEdit.text()
Form_login.hide()
Form_chat.show()
sock.connect(('127.0.0.1', 10000))
thread = QtCore.QThread()
listen1 = listen_thread()
listen1.moveToThread(thread)
listen1.listen_var.connect(update_chat_history)
thread.started.connect(listen1.run)
thread.start()
#QtCore.pyqtSlot(str)
def update_chat_history(message):
print(message)
ui_chat.textEdit_2.append(message)
def send_pressed():
message = ui_login.lineEdit.text() + ' > ' + ui_chat.lineEdit.text()
sock.send(bytes(str(message),'utf-8'))
# update_chat_history(message)
ui_chat.lineEdit.setText('')
def listen(some):
while True:
try:
data = sock.recv(1024)
except:
pass
else:
update_chat_history(str(data))
ui_login.pushButton.clicked.connect(connect_pressed)
ui_chat.pushButton.clicked.connect(send_pressed)
sys.exit(app.exec_())
When I'm launching it the output is:
QThread: Destroyed while thread is still running
Aborted (core dumped)
Can somebody help???
The explanation of your problem is simple: the thread created in connect_pressed has no reference outside that function, so it gets immediately garbage collected as soon as the function returns.
The "simple" solution would be to create the thread outside of that function and run the thread only there, or add the thread to a container (for instance, a list):
threads = []
def connect_pressed():
# ...
threads.append(thread)
But, the reality, is that your code has other serious issues, which would probably create other problems as soon as you try to expand your code even minimally, and that's also because Qt is easier to deal with when implementing the program using subclasses; even the pyqtSlot decorator (which is normally unnecessary) can only correctly work if it's set on a method of a QObject subclass.
Using only anonymous functions is usually discouraged, as they cannot have a direct reference to instances they must work with. Also, your run() function is wrong, as it doesn't have the self argument, which will result in a crash.
A better version of your code could be similar to this:
class LoginWindow(QtWidgets.QWidget, Ui_Form1):
def __init__(self, listenThread):
super().__init__()
self.setupUi(self)
self.pushButton.clicked.connect(listenThread.start)
class ChatWindow(QtWidgets.QWidget, Ui_Form):
def __init__(self, listenThread, loginWindow):
super().__init__()
self.listenThread = listenThread
self.loginWindow = loginWindow
self.setupUi(self)
self.listenThread.listen_var.connect(self.update_chat_history)
def update_chat_history(self, message):
print(message)
self.textEdit_2.append(message)
def send_pressed(self):
message = self.loginWindow.lineEdit.text() + ' > ' + self.lineEdit.text()
sock.send(bytes(str(message),'utf-8'))
self.lineEdit.setText('')
class ListenThread(QtCore.QThread):
listen_var = QtCore.pyqtSignal(str)
def run(self):
print('checkpoint')
while True:
listen_var.emit('message')
QtCore.QThread.msleep(1000)
listenThread = ListenThread()
loginWindow = LoginWindow(listenThread)
chatWindow = ChatWindow(listenThread, loginWindow)
sys.exit(app.exec_())

QtWebPageRenderer SIGILL issue

My problem is summed in title. When I call method setHtml on instance of QtWebPageRenderer, SIGILL signal is emitted and my application goes down.
I'm aware that this issue is caused by bad Qt5 dynamic library but I installed it with:
sudo pip install PyQt5 --only-binary PyQt5
sudo pip install PyQtWebEngine --only-binary PyQtWebEngine
so I thought I will get correct precompiled library. When I tried to install PyQt5 without --only-binary, I always ended with some strange compilation error. Something like qmake is not in PATH even though it is and I'm able to call qmake from shell.
So my question is, how to make PyQt5 running on Fedora 31 without any SIGILLs.
EDIT:
Following code can replicate the issue. That information about SIGILL is little inaccurate because first signal is actually SIGTRAP, after I hit continue in gdb, I got SIGILL. This hints that Qt is actually trying to say something to me, although in not very intuitive way.
After some playing around with it, I found that without thread, its ok. Does this mean that Qt forces user to use QThread and not python threads? Or it means that I can't call methods of Qt objects outside of thread where event loop is running?
import signal
import sys
import threading
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWebEngineWidgets import QWebEnginePage
class WebView(QWebEnginePage):
def __init__(self):
QWebEnginePage.__init__(self)
self.loadFinished.connect(self.on_load_finish)
def print_result(self, data):
print("-" * 30)
print(data)
with open("temp.html", "wb") as hndl:
hndl.write(data.encode("utf-8"))
def on_load_finish(self):
self.toHtml(self.print_result)
class Runner(threading.Thread):
def __init__(self, web_view):
self.web_view = web_view
threading.Thread.__init__(self)
self.daemon = True
def run(self):
self.web_view.load(QtCore.QUrl("https://www.worldometers.info/coronavirus/"))
def main():
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QtWidgets.QApplication(sys.argv)
web_view = WebView()
runner = Runner(web_view)
runner.start()
app.exec_()
if __name__ == "__main__":
main()
You have to have several restrictions:
A QObject is not thread-safe so when creating "web_view" in the main thread then it is not safe to modify it in the secondary thread
Since the QWebEnginePage tasks run asynchronously then you need a Qt eventloop.
So if you want to use python's Thread class then you must implement both conditions:
import signal
import sys
import threading
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWebEngineWidgets import QWebEnginePage
class WebView(QWebEnginePage):
def __init__(self):
QWebEnginePage.__init__(self)
self.loadFinished.connect(self.on_load_finish)
def print_result(self, data):
print("-" * 30)
print(data)
with open("temp.html", "wb") as hndl:
hndl.write(data.encode("utf-8"))
def on_load_finish(self):
self.toHtml(self.print_result)
class Runner(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.daemon = True
def run(self):
# The QWebEnginePage was created in a new thread and
# that thread has an eventloop
loop = QtCore.QEventLoop()
web_view = WebView()
web_view.load(QtCore.QUrl("https://www.worldometers.info/coronavirus/"))
loop.exec_()
def main():
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QtWidgets.QApplication(sys.argv)
runner = Runner()
runner.start()
app.exec_()
if __name__ == "__main__":
main()
In reality QThread and threading.Thread() are native thread handlers of the OS, so in practical terms it can be said that QThread is a threading.Thread() + QObject with an eventloop running on the secondary thread.
On the other hand, if your objective is to call a function from a thread to which it does not belong, then you should use asynchronous methods as pointed out in this answer.
In this case the simplest is to use pyqtSlot + QMetaObject:
import signal
import sys
import threading
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWebEngineWidgets import QWebEnginePage
class WebView(QWebEnginePage):
def __init__(self):
QWebEnginePage.__init__(self)
self.loadFinished.connect(self.on_load_finish)
def print_result(self, data):
print("-" * 30)
print(data)
with open("temp.html", "wb") as hndl:
hndl.write(data.encode("utf-8"))
def on_load_finish(self):
self.toHtml(self.print_result)
#QtCore.pyqtSlot(QtCore.QUrl)
def load(self, url):
QWebEnginePage.load(self, url)
class Runner(threading.Thread):
def __init__(self, web_view):
self.web_view = web_view
threading.Thread.__init__(self)
self.daemon = True
def run(self):
url = QtCore.QUrl("https://www.worldometers.info/coronavirus/")
QtCore.QMetaObject.invokeMethod(
self.web_view,
"load",
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(QtCore.QUrl, url),
)
def main():
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QtWidgets.QApplication(sys.argv)
web_view = WebView()
runner = Runner(web_view)
runner.start()
app.exec_()
if __name__ == "__main__":
main()
Or functools.partial() + QTimer
from functools import partial
import signal
import sys
import threading
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWebEngineWidgets import QWebEnginePage
class WebView(QWebEnginePage):
def __init__(self):
QWebEnginePage.__init__(self)
self.loadFinished.connect(self.on_load_finish)
def print_result(self, data):
print("-" * 30)
print(data)
with open("temp.html", "wb") as hndl:
hndl.write(data.encode("utf-8"))
def on_load_finish(self):
self.toHtml(self.print_result)
class Runner(threading.Thread):
def __init__(self, web_view):
self.web_view = web_view
threading.Thread.__init__(self)
self.daemon = True
def run(self):
wrapper = partial(
self.web_view.load,
QtCore.QUrl("https://www.worldometers.info/coronavirus/"),
)
QtCore.QTimer.singleShot(0, wrapper)
def main():
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QtWidgets.QApplication(sys.argv)
web_view = WebView()
runner = Runner(web_view)
runner.start()
app.exec_()
if __name__ == "__main__":
main()

PyQt5 Signals and Threading

I watched a short tutorial on PyQt4 signals on youtube and am having trouble getting a small sample program running. How do I connect my signal being emitted from a thread to the main window?
import cpuUsageGui
import sys
import sysInfo
from PyQt5 import QtCore
"""Main window setup"""
app = cpuUsageGui.QtWidgets.QApplication(sys.argv)
Form = cpuUsageGui.QtWidgets.QWidget()
ui = cpuUsageGui.Ui_Form()
ui.setupUi(Form)
def updateProgBar(val):
ui.progressBar.setValue(val)
class ThreadClass(QtCore.QThread):
def run(self):
while True:
val = sysInfo.getCpu()
self.emit(QtCore.pyqtSignal('CPUVALUE'), val)
threadclass = ThreadClass()
# This section does not work
connect(threadclass, QtCore.pyqtSignal('CPUVALUE'), updateProgBar)
# This section does not work
if __name__ == "__main__":
threadclass.start()
Form.show()
sys.exit(app.exec_())
The signal must be created, inside your ThreadClass, or before but as you emit the signal inside the ThreadClass, it is better to create it inside your class.
After creation, you need to connect it to the progress bar function. Here is an example of the signal created and connected inside your class.
class ThreadClass(QtCore.QThread):
# Create the signal
sig = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(ThreadClass, self).__init__(parent)
# Connect signal to the desired function
self.sig.connect(updateProgBar)
def run(self):
while True:
val = sysInfo.getCpu()
# Emit the signal
self.sig.emit(val)
Keep in mind that signals have changed style since PyQt5 : Description
if you watched a tutorial for PyQt4, it is not be the same.

Python: thread in PyQt, emit method to get the data

I try to get the value of Count_total_value in my user interface when I call Continusread method, (it should be "toto" according to what I want to do) but it is always the default value "azerty" which is displayed. Please can you tell me where I am wrong?
This is my code:
#!/usr/bin/env python3
from PyQt4 import QtCore, QtGui
import sys
import os
import subprocess
import time
import threading
from ctypes import *
import ctypes
#import Converted Python UI File
from test_error_rx import Ui_MainWindow
class MyThread(QtCore.QThread):
Count_total_valuechanged = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(MyThread, self).__init__(parent=parent)
self.Count_total_value = 'Azerty'
def run(self):
##do things to calculate Count_total_value
Count_total_value='toto'
self.Count_total_valuechanged.emit((self.Count_total_value))
time.sleep(0.1)
class Main( QtGui.QMainWindow,QtGui.QWidget):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# Connect the Buttons
QtCore.QObject.connect(self.ui.Continusread,QtCore.SIGNAL("clicked()"),self.Continusread)
self.__thread = MyThread(self)
self.__thread.Count_total_valuechanged.connect(self.ui.Count_total.setText)
def Continusread(self):
self.__thread.start()
def main():
app = QtGui.QApplication(sys.argv)
window = Main()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
In the run() method of your thread class MyThread you set Count_total_value='toto' when it should be self.Count_total_value='toto'.
Note that when posting on stackoverflow you should:
post a minimilistic working example (you haven't included your UI in the above code so no-one can run your script)
Check the posted code has the correct indentation and fix any mistakes (your posted code is a mess of incorrect indentation)

Categories