Wrap a Python class in an asynchronous PyQt class - python

I am developing my pet project. It is a small app which downloads my inbox folder from Facebook. I want it to be available in both CLI and GUI (in PyQt) mode. My idea was that I write a class for the communication, and then the front-ends. I know that the downloading process is blocking which is not problem in CLI mode but it is in GUI.
I know that there is QNetworkAccessManager but I would have to re-implement the class that I have already written and then maintain two classes the same time.
I was searching for a while and I came up with one solutions where I subclass QObject and my FB class, implement the signals, than create a QThread object and use moveToThread() method which would be OK, but I must take care of creating and stopping the tread.
Is it possible to wrap my Python class to something to make it behave like QNetworkAccessManager? So the methods would return immediately and the object will emit signals when the data is ready.
Update
Thank you the comments. I have 2 main classes. The first is called SimpleGraph which just hides urllib2.urlopen(). It prepares the query and returns the decoded json got from Facebook. The actual work is happening in FBMDown class:
class FBMDown(object):
def __init__(self, token):
self.graph = SimpleGraph(token)
self.last_msg_count = 0
def _message_count(self, thread_id):
#TODO: Sanitize thread_id
p = {'q': 'SELECT message_count FROM thread WHERE thread_id = {0} LIMIT 1'.format(thread_id)}
self.last_msg_count = int(self.graph.call(params=p, path='fql')['data'][0]['message_count'])
return self.last_msg_count
So here when _message_count is called it returns the number of messages of the given thread id. This method works great in CLI mode, blocking is not a problem there.
I want to wrap this class into a class (if it is possible) which works asynchronous like QNetworkAccessManager, so it would not block the GUI but it would emit a signal when the data is ready.
The only technique I know now is to subclass QObject. It looks like this:
class QFBMDown(QtCore.QObject):
msg_count_signal = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
def get_msg_count(self):
#Here happens the IO
time.sleep(2)
self.msg_count_signal.emit(1)
And this is my Windows class:
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.centralwidget = QtGui.QWidget(self)
self.button_getmsgcount = QtGui.QPushButton(self.centralwidget)
self.setCentralWidget(self.centralwidget)
self.i = 0
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.block_indicator)
self.timer.start(20)
#self.worker = QtCore.QThread()
self.fbobj = QFBMDown()
#self.fbobj.moveToThread(self.worker)
#self.worker.start()
self.button_getmsgcount.clicked.connect(self.fbobj.get_msg_count)
self.fbobj.msg_count_signal.connect(self.show_msg_count)
def block_indicator(self):
self.button_getmsgcount.setText(str(self.i))
self.i += 1
def show_msg_count(self, data):
print 'Got {0} msgs in the thread'.format(data)
Now when I press the button the GUI blocks. If I de-comment line 13, 15, 16 (where I create a thread, move fbobj into this thread then start it) it does not block, it works nearly as I want but I have to do everything by hand every time and I have to take care of shutting down the thread (now I implement QMainWindow's closeEvent method where I shut down the thread before quit but I'm sure that this is not the right way to do).
Is it even possible what I want to do? I would not want to implement a new class and then maintain two classes which are doing the same thing.
The reason I am so insisted to do it like this is to make my app work without Qt but give a nice gui for those who don't want to type command line arguments.

Related

Real-Time-Plotting using pyqtgraph and threading

this is a bit longer, the first part is just a description of the problem, the second one the question if my "fix" is correct.
I started with python programming. I created a program that communicates with an Arduino that reads the temperature of a furnace of our melting lab. The temperature is then used in a PID algorithm and an output is set to the Arduino. The communication is done via pyserial. So far, everthing works, including live plotting of the temperature signals, PID-variables and so on. The script includes a the main loop and 3 threads (serial communication, a datashifter that reads from serialport, the set temperature from the QWidget and the output of the PID algorithm. This values are used to create an array for displaying within pyqtgraph. Finally, the third thread shifts the data from the datashifter to the QWidget.
When using my Linux-Notebook, everything works fine, and the GUI never stops updating. In contrast, when using any Windows-Host, i have the problem that some pyqtgraphs stop to refresh. The behavior is strange, because i set all data at more or less the same time, with the same numpy array (just different columns) - some plots refresh longer (hours), some stop earlier (minutes). After searching more or less the hole internet ;-) I think that I found the problem: Its the passing of data from from a thread to the GUI. Some dummy code to explain what's going on:
DataUpdaterToGUI(QThread):
#sets the QWidget from main loop
def setGUI(self, gui):
self.gui = gui
def run()
while True:
with lock(): # RLock() Instance
copyArray = self.dataArray[:] # copy the array from the shifter
self.gui.plot1.copyArray(dataArray[:, 0], copyArray[:, 1])
self.gui.plot2.copyArray(dataArray[:, 0], copyArray[:, 2])
# self.gui.update()
# QApplication.instance().processEvents()
Neither calling self.gui.update() nor processEvents() has any influence on the outcome: The plots stop redrawing after a while (on windows).
Now i have a very simple example, and just want to make sure if I'm using the threading-stuff correctly. It works fine, but I have some questions:
Does the signal-slot approach copy the passed data?
Why is it not necessary to call the update() method of the QWidget?
Do I have to use any kind of locks when using signals?
class Main(QWidget):
def __init__(self):
super().__init__()
self.layout = QGridLayout(self)
self.graph = pg.PlotWidget()
self.graph.setYRange(0,1000)
self.plot = self.graph.plot()
self.layout.addWidget(self.graph,0,0)
self.show()
def make_connection(self, data_object):
data_object.signal.connect(self.grab_data)
#pyqtSlot(object)
def grab_data(self, data):
print(data)
self.plot.setData(data)
class Worker(QThread):
signal = pyqtSignal(object)
def __init__(self):
super().__init__()
def run(self):
self.data = [0, 1]
i = 2
while True:
self.data[1] = i
self.signal.emit(self.data)
time.sleep(0.01)
i += 1
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = Main()
worker = Worker()
widget.make_connection(worker)
worker.start()
sys.exit(app.exec_())
Does the signal-slot approach copy the passed data? The signals are thread-safe and when transferring data they make a copy so the thread that precedes the data and the thread that consumes it (GUI Thread) will not have conflicts
Why is it not necessary to call the update() method of the QWidget? Actually pyqtgraph calls the update method, plot is a PlotDataItem, so if we check the source code of setData() method, it calls the updateItems() method, in that method the setData() method of the curve or scatter attribute is called (according to the type of graphics), in the case of curve its setData() method calls updateData(), and the updateData() method calls update, and in the case of the scatter its setData() method calls addpoint(), and addPoints() calls invalidate(), and this invalidate() method calls update().
Do I have to use any kind of locks when using signals? No, as the signals are thread-safe so Qt already has the guards set to avoid the collision.

PyQT force update textEdit before calling other function

My question concerns PyQT5.
I try to have a dialog window with a button that when clicked
updates some text of a QTextEdit field
calls a function (which needs much time to terminate)
Something like this:
class StartDialog(QtWidgets.QWidget, start_dialog_ui.Ui_Dialog):
def __init__(self, parent):
super(self.__class__, self).__init__()
self.setupUi(self)
self.OKButton.clicked.connect(self.start)
def start(self):
self.startDialogTextEdit.append("simulation running ...")
run_lengthy_function(self)
However, when I run my GUI I notice that the text is updated only after the lengthy function has terminated, although the QTextEdit.append is called before the lengthy function. How can I enforce that the text is updated in advance?
What I tried so far (but didn't work) was to let Python wait some time before triggering the lengthy function call, i.e.
from time import sleep
class StartDialog(QtWidgets.QWidget, start_dialog_ui.Ui_Dialog):
def __init__(self, parent):
super(self.__class__, self).__init__()
self.setupUi(self)
self.OKButton.clicked.connect(self.start)
def start(self):
self.startDialogTextEdit.append("simulation running ...")
sleep(5)
run_lengthy_function(self)
The repaint is called in event loop so sleep the whole thread does not change anything.
You can call repaint manually by:
self.startDialogTextEdit.repaint()
or call static method:
QCoreApplication.processEvents()
which also call repaint internally
The solution for the case that the text is displayed in the QTextEdit is to call qApp.processEvents(), this force to the GUI update:
def start(self):
self.startDialogTextEdit.append("simulation running ...")
QtWidgets.qApp.processEvents()
[...]
On the other hand if the task is heavy it may be blocking the GUI, so maybe one solution is to run it on another thread, I can not give a proper recommendation since I do not know your function

PyQt - How can I re-initialise the whole code without restarting it?

I wrote a python GUI program consisting of two separate files; One is for logic code and the other for GUI using PyQt4. The behaviour of some objects (buttons, text fields ...) changes throughout the code and I need to reset everything to its original status by clicking on a QAction class menu item. How can I do that?
EDIT: the function that supposed to reset the GUI to the original status:
def newSession(self):
self.ui.setupUi(self)
self.filename = ""
self.paramsSplitted = []
self.timestep = None
self.index = None
self.selectedParam = None
self.selectedMethod = None
--Snip--
What you could do:
Define a ResetHandler(QtCore.QObject) object with a reset_everything signal
During startup create an instance and set it on the globally available QApplication like qapplication.reset_handler = ResetHandler()
Every UI element that needs to update itself defines a on_reset_everything_triggered() slot. (Optional: You could also just use update for example).
When You create UI elements that are supposed to update, connect them to the globally available reset_everything signal from the handler on the QApplication.
Connect your QAction.triggered with the ResetHandler.reset_everything signal.
Now every time you press the QAction the reset_everything signal is invoked, and all UI elements that you connected will update themself.
Like you requested in your comment here is a schematic way of utilizing a function to connect all signals and the method setupUi.
class MainWindow(QtGui.QMainWindow) :
def __init__(self) :
QtGui.QMainWindow.__init__(self)
self.ui.setupUi(self)
# Some code
self.connectAllSignals()
def connectAllSignals(self) :
self.someWidget.clicked.connect(self.someFunction)
self.someAction.triggered.connect(self.otherFunction)
# All the other signals
def disconnectAllSignals(self) :
try :
self.someWidget.clicked.disconnect()
self.someAction.triggered.disconnect()
# All the other signals
except :
print("Something went wrong. Check your code.")
pass
def newSession(self) :
self.ui.setupUi(self)
self.disconnectAllSignals()
self.connectAllSignals()
# Do whatever it takes
By this you ensure you have only the initial settings for your signals and all dynamically added connections are broken. In the method disconnectAllSignals be sure all widgets exist and all signals have at least one connection by the time you call it. If you have new widgets invoked dynamically you should consider deleting them in th method newSession after calling connectAllSignals.

Signals and Slots PyQt clarification

I have noticed that there are a lot of users, myself included, who don't quite grasp the concept of signals and slots in Qt. I was hoping to get some clarification on the following:
#I have a function that runs as soon as the GUI is built, this takes the information from
#a list and puts it into a string which is then uploaded to a texbox. At the bottom of this
#loop, I want it to call a function in the parent thread via signals and slots, as
#recommended by other users.
class MainWindow(QtGui.QMainWindow):
#all the code needed to build the GUI
thread_mythread = threading.Thread(target = self.updateText, args = ())
thread_mythread.start()
def clearText(self):
self.TextEdit.clear()
def updateText(self):
self.trigger.connect(self.clearText)
while True:
self.trigger.emit()
NewString = list.pop(0)
#I think I may have to use append, as setText() is not safe outside of the parent thread
self.TextEdit.append(NewString)
Although probably terribly incorrect, I attempt to use signals. Is this the proper way to do it? I also get an error that says that the Main Window object has no attribute "trigger",why is this?
thank you.
The reason you get that error is exactly the reason described by the error message - the signal trigger has not been defined anywhere in your class. You need to define it before you can emit it.
Signals and slots are used to communicate between different objects. In your example you are trying to do everything from within your MainWindow class and there is no interaction with other objects. You also only need to make the call to connect() once. You would typically call this either in the class constructor or from your main function after instantiating the objects you want to connect together.
Take a look at http://pyqt.sourceforge.net/Docs/PyQt4/new_style_signals_slots.html for some examples of how to use signals and slots properly in PyQt.
For threading, use QThread rather than threading.Thread as it is better integrated with the Qt framework. This post shows some simple examples of how to use QThread in PyQt. The second method (using moveToThread()) is considered to be the most correct way to create new threads.
The basic idea for your kind of problem is:
handle GUI operations from the main thread
handle blocking operations (in your case the while loop) in a separate thread
emit signals from the worker thread to call functions (slots) in the main thread and vice versa
Also note that:
You cannot call any methods of QWidget its descendents from a secondary thread
Signals can also send data if you need to pass it between threads
To add to #user3419537 good answer. A very quick threading example:
from PyQt4.QtCore import QObject, pyqtSlot, pyqtSignal, QThread, \
Q_ARG, Qt, QMetaObject
class MyWorker(QObject):
# define signal
clear = pyqtSignal()
update_text_signal = pyqtSignal(str) # passes a string back
finished = pyqtSignal()
def __init__(self, parent=None):
super(MyWorker, self).__init__(parent)
# Add functions etc.
#pyqtSlot(list)
def update_text(self, string_list):
#Intensive operation
self.clear.emit() # moved outside of while
while(True):
#This is infinite loop so thread runs forever
new_string = self.string_list.pop(0)
self.update_text_signal.emit(new_string) # Fixed this line
#Finished
self.finished.emit()
Then in your MainWindow class
self.my_thread = QThread()
self.handler = MyWorker()
self.handler.moveToThread(self.my_thread)
self.handler.clear.connect(self.clearText)
self.handler.update_text_signal.connect(self.update_line_edit)
self.handler.finished.connect(self.my_thread.quit)
# Start Thread
self.my_thread.start()
#pyqtSlot(str)
def update_line_edit(self, text):
self.TextEdit.append(text)
QMetaObject.invokeMethod(self.handler, 'update_text',
Qt.QueuedConnection,
Q_ARG(list, string_list))
You will need to call self.my_thread.quit() before your application closes to stop the thread and avoid the error: QThread: Destroyed while thread is still running
Please read docs for QMetaObject.invokeMethod.

wxpython frame initialization error with threads

Here's an example:
class DemoFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
self.panel = wx.Panel(self, -1)
...
initialize other elements
...
self.DoStuff()
def DoStuff(self):
self.panel.SetBackGroundColour(wx.Colour(240, 240, 240))
...
do something
...
Now as you know this is definitely not a good example of initializing your GUI since do something would most probably freeze the GUI while it's running, so I tweaked it to this:
import threading
class DemoFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
self.panel = wx.Panel(self, -1)
...
initialize other elements
...
DoStuffThead = threading.Thread(target = self.DoStuff, ())
DoStuffThead.start()
def DoStuff(self):
wx.CallAfter(self.ChangeBG, )
...
do something
...
def ChangeBG(self):
self.panel.SetBackGroundColour(wx.Colour(240, 240, 240))
Above code should work exactly the same as the first one does when do something is blank, but to my surprise I noticed there's little background drawing glitches when running the latter codes.
What part went wrong? Isn't this the right way to update GUI in threads?
It's a bad approach to update GUI from worker thread, not event saying it's not thread safe.
You have to communicate with main thread to update GUI.
The best way to achieve desired result is to user wx.PostEvent method. You can create custom events for your needs, inheriting from wx.PyEvent, and you better inherit threading.Thread to keep window you want to communicate within that thread class as an instance variable.
The best illustration of how to update GUI having long-running task can be found in wxPython wiki (first example).
After searching and playing with wxpython for a while, I finally found a solution for this, and it's actually quite simple, just refresh the panel and everything will be all right(add this line into ChangeBG method): self.panel.refresh(). I've no idea why the glitch exists though.
As to Rostyslav's answer, thanks a lot mate!
"It's a bad approach to update GUI from worker thread:", I think you mean it's rude to directly insert GUI codes into worker thread(which is exactly what I did in the first example) in terms of thread-safety concern, basically those GUI codes should be wrapped into thread-safe method(which is exactly what I was trying to do in my second example) and then queued into GUI main thread.
I found that there are basically three thread-safe methods as to GUI updating in worker thread: wx.PostEvent, wx.CallAfter and wx.CallLater, but I never liked wx.PostEvent, it's kind of cumbersome and you have to come up with your own event too, that's why wx.CallAfter is a better choice for me, it's more pythonic and easy to use, and actually wx.CallAfter is like a high level wrapper for wx.PostEvent if you check out the source code in _core.py:
def CallAfter(callable, *args, **kw):
"""
Call the specified function after the current and pending event
handlers have been completed. This is also good for making GUI
method calls from non-GUI threads. Any extra positional or
keyword args are passed on to the callable when it is called.
:see: `wx.CallLater`
"""
app = wx.GetApp()
assert app is not None, 'No wx.App created yet'
if not hasattr(app, "_CallAfterId"):
app._CallAfterId = wx.NewEventType()
app.Connect(-1, -1, app._CallAfterId,
lambda event: event.callable(*event.args, **event.kw) )
evt = wx.PyEvent()
evt.SetEventType(app._CallAfterId)
evt.callable = callable
evt.args = args
evt.kw = kw
wx.PostEvent(app, evt)
Well, I never tried wx.PostEvent implementation in my app, but I'm sure it would work as well.
Oh also I found this article very helpful: wxPython and Threads

Categories

Resources