I am trying to run PyQt (PyQt6.4.0) server with fastapi (0.89.1) and uvicorn (0.20.0). The code is as following:
# PyQt with server code
from fastapi import FastAPI
import threading
import uvicorn
from PyQt6.QtWidgets import (QMainWindow, QApplication, QTextEdit)
import sys
from PyQt6.QtCore import (QRect)
# main window app in main thread
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setGeometry(QRect(0, 0, 400, 400))
self._initial_widgets()
self._create_server()
def _initial_widgets(self):
self.textedit = QTextEdit(self)
self.textedit.setGeometry(QRect(100, 0, 100, 100))
self.textedit.setReadOnly(True)
self.setCentralWidget(self.textedit)
def _create_server(self):
self.app = FastAPI()
#self.app.post("/")
async def change_textEdit_data():
self.textedit.setText("hello") # By removing this important line there is no issue
return {"detail": "OK"}
# The server is going to be run in a child thread
# I believe that this is the source of the issue
# Child thread does not have access to self in parent thread
thread = threading.Thread(target=uvicorn.run, kwargs={"app": self.app, "port": 80})
thread.start() # runs server in separate thread
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
And to send a request to it
# Code to send test signal
from time import sleep
import requests as req
if __name__ == "__main__":
sleep(1)
r = req.post(url="http://127.0.0.1:80")
print(r.text, r.status_code)
Running order
PyQt with server code
Code to send test signal
The server has to be run in a thread to avoid UI freezing. (The freezing is not the actual issue)
The issue is that after I start the 2nd code in another process the following is appearing in console of the first one:
INFO: Started server process [14400]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:80 (Press CTRL+C to quit)
Process finished with exit code -1073741819 (0xC0000005)
It crashes.
Just by removing line self.textedit.setText("hello"), the issue is fixed, but the line is important.
Could someone suggest on what should I use to be able to change text in the textbox by using data received from API in PyQt app.
It is important that I keep PyQt and for the other libraries some other options can be suggested, i.e. sth instead of uvicorn.
Edit:
After I remove self.textedit.setText("hello") I am getting an expected reply
{"detail":"OK"} 200
So, the issue is definitely something with that line.
As suggested by #musicamante, the solution is simple:
Create a signal and create an instance of that object as below:
class Signals(QObject):
text_signal = pyqtSignal(str)
signals = Signals()
Create a function in the MainWindow class to change the text as follows:
class MainWindow(QMainWindow):
...
def set_textEdit(self, data):
self.textedit.setText(data)
Connect the signal and the function:
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
...
signals.text_signal.connect(self.set_textEdit)
Create implementation of emit in the API
class MainWindow(QMainWindow):
...
def _create_server(self):
...
#self.app.post("/")
async def change_textEdit_data(data):
signals.text_signal.emit(data)
return {"detail": "OK"}
...
Everything else remains the same and the final code is
# PyQt with server code
from fastapi import FastAPI
import threading
import uvicorn
from PyQt6.QtWidgets import (QMainWindow, QApplication, QTextEdit)
import sys
from PyQt6.QtCore import (QRect, pyqtSignal, QObject)
class Signals(QObject):
text_signal = pyqtSignal(str)
signals = Signals()
# main window app in main thread
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setGeometry(QRect(0, 0, 400, 400))
self._initial_widgets()
self._create_server()
signals.text_signal.connect(self.set_textEdit)
def _initial_widgets(self):
self.textedit = QTextEdit(self)
self.textedit.setGeometry(QRect(100, 0, 100, 100))
self.textedit.setReadOnly(True)
self.setCentralWidget(self.textedit)
def _create_server(self):
self.app = FastAPI()
#self.app.post("/")
async def change_textEdit_data(data):
signals.text_signal.emit(data)
return {"detail": "OK"}
thread = threading.Thread(target=uvicorn.run, kwargs={"app": self.app, "port": 81})
thread.start()
def set_textEdit(self, data):
self.textedit.setText(data)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Related
I want to write a python program run on background, and displaying PyQt5 GUI from background process on neccessary.
My solution is use RabbitMQ to do IPC work. program start with PyQt run on main thread, and start a thread listening RabbitMQ to show GUI on call.
Here is the code:
from PyQt5.QtWidgets import QApplication, QLabel
from PyQt5.QtCore import QThreadPool, QObject, QRunnable, pyqtSignal
import traceback
import pika
import sys
class RabbitMQSignals(QObject):
target = pyqtSignal(int)
class MessageListener(QRunnable):
def __init__(self):
super(MessageListener, self).__init__()
self.signals = RabbitMQSignals()
def run(self):
self.connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
self.channel = self.connection.channel()
self.channel.queue_declare(queue='ui')
self.channel.basic_consume(queue='ui', on_message_callback=self.dispatch, auto_ack=True)
print('Waiting for signals')
self.channel.start_consuming()
def dispatch(self, channel, method, properties, body):
body = body.decode('utf-8')
if body == 'quit':
sys.exit(0)
print('[x] Received %s' % body)
self.signals.target.emit(0)
class MainWidget(QObject):
def __init__(self):
super(MainWidget, self).__init__()
def show(self, action):
try:
print('[x] Dispatched :' + str(action))
label = QLabel('Hello World')
label.show()
except:
print(traceback.format_exc())
if __name__ == '__main__':
app = QApplication([])
widget = MainWidget()
pool = QThreadPool()
listener = MessageListener()
listener.signals.target.connect(widget.show)
pool.start(listener)
app.exec_()
Now, everything works fine except that the label.show line crashes the program, no widget displayed, no message printed.
The client part is listed below, sending quit to quit the server.
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='ui')
channel.basic_publish(exchange='', routing_key='ui', body='show label')
connection.close()
My question is, how and why the label.show() shutdown the program without any error? How can I improve the program?
Or is there alternative ways to do the job?
Any suggestions are welcome.
Thank you in advance.
The problem is that QLabel is a local variable so it will be deleted a moment after the show method is executed. The above should not cause the application to terminate but by default QApplication will be closed if at any time after showing at least one window they all close, in your case QLabel is displayed for a moment and closes, which consequently implies that the application it closes.
So the solution is to make the label a member of the class so that it is not deleted:
# ...
class MainWidget(QObject):
def __init__(self):
super(MainWidget, self).__init__()
self.label = QLabel("Hello World")
def show(self, action):
print('[x] Dispatched :' + str(action))
self.label.show()
# ...
On the other hand, Qt does not return exceptions when some function/method fails in its task, instead it informs us of the success of the execution through a variable, this does it for reasons of efficiency. And that same PyQt inherits so in 99.99% of the python code is not necessary to use try-except.
I have a small non-GUI application that basically starts an event loop, connects a signal to a slot, and emits a signal. I would like the slot to stop the event loop and exit the application.
However, the application does not exit.
Does anyone have any ideas on how to exit from the event loop?
Python 3.7.0
Qt for Python (PySide2) 5.12.0
import sys
from PySide2 import QtCore, QtWidgets
class ConsoleTest(QtCore.QObject):
all_work_done = QtCore.Signal(str)
def __init__(self, parent=None):
super(ConsoleTest, self).__init__(parent)
self.run_test()
def run_test(self):
self.all_work_done.connect(self.stop_test)
self.all_work_done.emit("foo!")
#QtCore.Slot(str)
def stop_test(self, msg):
print(f"Test is being stopped, message: {msg}")
# neither of the next two lines will exit the application.
QtCore.QCoreApplication.quit()
# QtCore.QCoreApplication.exit(0)
return
if __name__ == "__main__":
app = QtCore.QCoreApplication(sys.argv)
mainwindow = ConsoleTest()
sys.exit(app.exec_())
When you call app.exec_() you are entering the Qt event loop, but since you are executing the code that call quit() at the moment the object is being initialized, your application will never quit.
I can think of two options to achieve the "exit" process you want to do:
Just call sys.exit(1) inside the stop_test() instead of a quit or exit call, or
you use singleShot to quit the application:
import sys
from PySide2 import QtCore, QtWidgets
class ConsoleTest(QtCore.QObject):
all_work_done = QtCore.Signal(str)
def __init__(self, parent=None):
super(ConsoleTest, self).__init__(parent)
self.run_test()
def run_test(self):
self.all_work_done.connect(self.stop_test)
self.all_work_done.emit("foo!")
#QtCore.Slot(str)
def stop_test(self, msg):
print(f"Test is being stopped, message: {msg}")
QtCore.QTimer.singleShot(10, QtCore.qApp.quit)
if __name__ == "__main__":
app = QtCore.QCoreApplication(sys.argv)
mainwindow = ConsoleTest()
sys.exit(app.exec_())
Problem Description
I'm trying to make an application that collects data, processes it, displays it, and some actuation (open/close valves, etc). As a practice for future applications where I have some stricter time constraints, I want to run the data capture and processing in a separate thread.
My current problem is that it's telling me I cannot start a timer from another thread.
Current code progress
import sys
import PyQt5
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QThread, pyqtSignal
# This is our window from QtCreator
import mainwindow_auto
#thread to capture the process data
class DataCaptureThread(QThread):
def collectProcessData():
print ("Collecting Process Data")
#declaring the timer
dataCollectionTimer = PyQt5.QtCore.QTimer()
dataCollectionTimer.timeout.connect(collectProcessData)
def __init__(self):
QThread.__init__(self)
def run(self):
self.dataCollectionTimer.start(1000);
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self) # gets defined in the UI file
self.btnStart.clicked.connect(self.pressedStartBtn)
self.btnStop.clicked.connect(self.pressedStopBtn)
def pressedStartBtn(self):
self.lblAction.setText("STARTED")
self.dataCollectionThread = DataCaptureThread()
self.dataCollectionThread.start()
def pressedStopBtn(self):
self.lblAction.setText("STOPPED")
self.dataCollectionThread.terminate()
def main():
# a new app instance
app = QApplication(sys.argv)
form = MainWindow()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Any advice on how to get this to work would be appreciated!
You have to move the QTimer to the DataCaptureThread thread, in addition to that when the run method ends, the thread is eliminated so the timer is eliminated, so you must avoid running that function without blocking other tasks. QEventLoop is used for this:
class DataCaptureThread(QThread):
def collectProcessData(self):
print ("Collecting Process Data")
def __init__(self, *args, **kwargs):
QThread.__init__(self, *args, **kwargs)
self.dataCollectionTimer = QTimer()
self.dataCollectionTimer.moveToThread(self)
self.dataCollectionTimer.timeout.connect(self.collectProcessData)
def run(self):
self.dataCollectionTimer.start(1000)
loop = QEventLoop()
loop.exec_()
I am running into an issue when I am taking in values over serial and then attempting to update my Gui with those values. Unfortunately, even though the values update correctly, I am unable to get to the screen to refresh unless I click off of it and then back on to it. I have tried repaint, update, and processEvents() but have been unable to solve the problem.
Here is the code I am working with:
import sys
import serial
import time
import requests
import PyQt5
from PyQt5.QtWidgets import *
from PyQt5.QtCore import*
from PyQt5.QtGui import *
import mainwindow_auto
CUSTOM_EVENT = 1000
ser = serial.Serial('/dev/ttyACM0', 9600)
class TestThread(QThread):
def __init__(self, target):
QThread.__init__(self)
self.target = target
def run(self):
while True:
QApplication.postEvent(self.target, QEvent(QEvent.Type(CUSTOM_EVENT)))
QApplication.processEvents()
QThread.sleep(15)
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)# gets defined in the UI file
self.thread = TestThread(self)
self.thread.start()
def event(s, e):
if(e.type() == CUSTOM_EVENT):
print("Readline: ",int(ser.readline()))
SOC = int(ser.readline())
s.lcdNumber.display(SOC)
s.progressBar.setValue(SOC)
print("SOC: ",SOC)
print(s.lcdNumber.value())
return True
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.lcdNumber.display(30)
form.progressBar.setValue(30)
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Thanks in advance!
Since you already have an I/O thread, let it handle the I/O and sent the received value to the main thread via a signal.
No need for a custom event, no I/O on the main thread.
Just adding a signal to the thread subclass and connecting a slot to that before starting the thread.
Rather than rewriting the code that I had above, I ended up fixing it by force redrawing using s.hide() and s.show() after updating the values in the event code. It forced a redraw that otherwise refused to work.
s.lcdNumber.display(SOC)
s.progressBar.setValue(SOC)
s.hide()
s.show()
As suggested by #KevinKrammer, this is simple to do with a custom signal:
class TestThread(QThread):
serialUpdate = pyqtSignal(int)
def run(self):
while True:
QThread.sleep(1)
value = int(ser.readline())
self.serialUpdate.emit(value)
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUi(self)
self.thread = TestThread(self)
self.thread.serialUpdate.connect(self.handleSerialUpdate)
self.thread.start()
def handleSerialUpdate(self, value):
print("Readline: ", value)
self.lcdNumber.display(value)
self.progressBar.setValue(value)
I am trying to save image of a page on a http request to a flask server.
This is the message that I get on running the thing
QObject: Cannot create children for a parent that is in a different thread.
here is my code
import sys
import time
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *
import Image
from flask import Flask, Response, jsonify,request
app=Flask(__name__)
class Screenshot(QWebView):
def __init__(self):
self.app = QApplication(sys.argv)
QWebView.__init__(self)
self._loaded = False
self.loadFinished.connect(self._loadFinished)
def capture(self,url,width,output_file):
self.resize(width,300)
self.load(QUrl(url))
self.wait_load()
# set to webpage size
frame = self.page().mainFrame()
self.page().setViewportSize(frame.contentsSize())
# render image
image = QImage(self.page().viewportSize(), QImage.Format_ARGB32)
painter = QPainter(image)
frame.render(painter)
painter.end()
print 'saving', output_file
image.save(output_file)
def wait_load(self, delay=0):
# process app events until page loaded
while not self._loaded:
self.app.processEvents()
time.sleep(delay)
self._loaded = False
def _loadFinished(self, result):
self._loaded = True
if __name__=='__main__':
s = Screenshot()
#app.route('/image', methods=['GET','POST'])
def getPicture():
#reading args
url=request.args.get('url')
screenWidth=int(request.args.get('sw'))
top=int(request.args.get('top'))
left=int(request.args.get('left'))
width=int(request.args.get('width'))
height=int(request.args.get('height'))
#cropping image
s.capture(url,screenWidth,"temp.png")
img=Image.open("temp.png")
box=(left, top, left+width, top+height)
area=img.crop(box)
area.save("output","png")
return "output.png"
#app.route('/')
def api_root():
return 'Welcome'
app.run(host='0.0.0.0',port=3000,debug=True)
whenever I hit this using
curl http://0.0.0.0:3000/image?url=googlecom&sw=1920&top=100&left=100&width=200&height=200
I get the following error message,
QObject: Cannot create children for a parent that is in a different thread.
(Parent is Screenshot(0x7f9dbf121b90), parent's thread is QThread(0x7f9dbdb92240), current thread is QThread(0x7f9dbf11ed50)
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
I'm not sure that is the reason, but I don't see a self.app.exec_() call in Screenshot.__init__(). Without it, the QApplication instance never enters the main loop. BTW, I would instantiate QApplication outside of Screenshot but somewhere in your 'main' section. Not sure if it matters in this particular case, but it may.