I am planning a PySide Project which has a user authentication. Some of the operations of the program will require threads in order to keep the GUI responsive. Some of the operations which are done inside the threads require the user id or some other user data.
The main question is how to make those data available for the threads. The threads will not change the user id and there are only reading operations on the given user data.
The code below is a very simple mock up to illustrate the problem. The User class returns the user data if the login was successful. They will be stored as class variable inside the Interface class. As soon the Thread/QRunnable is initialized the user data are given to the worker and stored as class variable for the lifetime of the thread.
I couldn't find any clear answer to:
Is reading the variable inside the thread "thread safe" (As reading from a dict is an atomic operation it should be)
Is there a better way to make data like this available to a bigger project? e.g. Config files, User data...
Simple example:
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtCore import QObject, QRunnable, QThreadPool
class Worker(QRunnable):
def __init__(self, current_user):
super().__init__()
self.current_user = current_user
def run(self) -> None:
print(self.current_user)
class User():
def login(self, user: str, password: str):
return {'user_id': 1, 'user_name': f'{user}'}
class Interface(QMainWindow):
def __init__(self):
super().__init__()
self.thread_pool = QThreadPool.globalInstance()
self.user = User()
self.current_user = self.user.login('John Doe', '123456')
worker = Worker(self.current_user)
self.thread_pool.start(worker)
self.show()
if __name__ == '__main__':
app = QApplication([])
interface = Interface()
app.exec()
The ID of the user data stays always the same so all workers will operate on the same memory space as far as I understood
Related
I am writing a package to send/receive frames over a CAN bus monitored by a GUI, using pyqt5.
Here is the process :
Create A new QThread object (called tx_thread)separate from main window thread.
Create a TxWorker (class inheriting from QObject), called tx_worker.
Move it to tx_thread
Setup signal/slot connexion
Start tx_thread
Create a TxJob object (class inheriting from QObject)
Move it to tx_thread
Pass it to tx_worker through signal/slot connexion, so that tx_worker handles the TxJob by itself afterwards.
I have used the above process many times for other cases and it usually works perfectly.
However, in this case I get an error : it seems that the TxJob object "loses" its type during the call through signal/slot and is reverted to a mere QObject without the additional TxJob attributes. That is very weird to me because I specified the type of signal parameter and slot signature.
Does anyone understand what the issue is and what I should do to fix it ?
-- EDIT --
I have found a workaround to avoid the error : I send constructor parameters instead of the object in the signal emit method, then I create the TxJob object from within the receiving thread.
Nevertheless, I want to understand why my first approach did not work, for the sake of my knowledge in python and/or Qt. I would be glad to get any leads on this.
--
I condensed my code into the following files to reproduce the issue.
When executing test_main.py, I get :
Before emitting signal, type is : <class 'test_tx_job.TxJob'>
After entering slot, type is : <class 'PyQt5.QtCore.QObject'>
'QObject' object has no attribute 'set_active'
The error arises when executing job.set_active(True) in method add_tx_job() of class TxWorker
test_main.py
import sys
from test_iocan import IoCan
from PyQt5.QtWidgets import QApplication
def main():
app = QApplication(sys.argv)
io_can = IoCan()
io_can.new_tx_task()
io_can.show()
sys.exit(app.exec_()) # Start event loop
if __name__ == '__main__':
main()
test_iocan.py
from test_tx_job import TxJob
from test_tx_worker import TxWorker
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QMainWindow
class IoCan(QMainWindow):
sig_add_tx_job = pyqtSignal(TxJob)
def __init__(self):
super(IoCan, self).__init__()
self.tx_thread = QThread(parent=self) # Step 0
self.tx_worker = TxWorker() # Step 1
self.tx_worker.moveToThread(self.tx_thread) # Step 2
self.sig_add_tx_job.connect(self.tx_worker.add_tx_job) # Step 3
self.tx_thread.start(priority=QThread.TimeCriticalPriority) # Step 4
def closeEvent(self, event):
"""Override of the close event default method."""
# Needed to cleanly terminate threads
self.threads_stop()
super(IoCan, self).closeEvent(event)
def new_tx_task(self):
new_job = TxJob() # Step 5
new_job.moveToThread(self.tx_thread) # Step 6
print('Before emitting signal, type is : ' + str(type(new_job)))
self.sig_add_tx_job.emit(new_job) # Step 7
def threads_stop(self):
self.tx_thread.quit()
self.tx_thread.wait()
test_tx_worker.py
from test_tx_job import TxJob
from PyQt5.QtCore import QObject, pyqtSlot
class TxWorker(QObject):
def __init__(self):
super(TxWorker, self).__init__()
self.tx_jobs = []
#pyqtSlot(TxJob)
def add_tx_job(self, job: TxJob): # Called at step 7 in tx_thread
print('After entering slot, type is : ' + str(type(job)))
try:
job.set_active(True)
except AttributeError as err:
print(err.args[0])
else:
print(type(job))
self.tx_jobs.append(job)
test_tx_job.py
from PyQt5.QtCore import QObject
class TxJob(QObject):
def __init__(self):
super(TxJob, self).__init__()
self._is_active = False
def set_active(self, value: bool):
self._is_active = value
I try to use QRemoteObjects to share more than two objects but
i got "Dynamic metaobject is not assigned" warning while run client.py example, and i can't find out what happened, my example work fine, can anyone give me some advices?
server.py
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtRemoteObjects import *
from faker import Faker
fake = Faker()
class Name(QObject):
sig_name = pyqtSignal(str)
def __init__(self):
super().__init__()
self.name = ''
self.startTimer(1000)
def timerEvent(self, event):
self.name = fake.name()
self.sig_name.emit(self.name)
class Email(QObject):
sig_email = pyqtSignal(str)
def __init__(self):
super().__init__()
self.startTimer(1000)
def timerEvent(self, event):
self.sig_email.emit(fake.email())
class Server(QObject):
def __init__(self):
super().__init__()
self.name = Name()
self.email = Email()
host = QRemoteObjectHost(QUrl('local:server'), self)
r1 = host.enableRemoting(self.name, 'name')
r2 = host.enableRemoting(self.email, 'email')
print([r1, r2])
def print_name(self, x):
print(x)
app = QCoreApplication([])
s = Server()
app.exec()
client.py
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtRemoteObjects import *
class Client(QObject):
def __init__(self):
super().__init__()
node = QRemoteObjectNode(self)
node.connectToNode(QUrl("local:server"))
self.remote_name = node.acquireDynamic('name')
self.remote_email = node.acquireDynamic('email')
self.remote_name.initialized.connect(self.onInitName)
self.remote_email.initialized.connect(self.onInitEmail)
def onInitName(self):
self.remote_name.sig_name.connect(self.print_info)
def onInitEmail(self):
self.remote_email.sig_email.connect(self.print_info)
def print_info(self, x):
print('-->:', x)
app = QCoreApplication([])
c = Client()
app.exec()
After i run python server.py in terminal one and run python client.py in terminal two.
I got some warning as below in terminal two.
In C++ you can purchase the replica using 2 methods:
QRemoteObjectNode::acquire():
SimpleSwitchReplica *rep = repNode.acquire<SimpleSwitchReplica>("SimpleSwitch"));
QRemoteObjectNode::acquireDynamic():
QRemoteObjectDynamicReplica *rep = repNode.acquireDynamic("SimpleSwitch");
As the second method is observed, a QRemoteObjectDynamicReplica is used which is an object that is class created on-the-fly by copying the properties, signals and slots but does not contain all the information of the node class so it is not an exact copy so which has disadvantages as the docs points out:
There are generated replicas (replicas having the header files
produced by the Replica Compiler), and dynamic replicas, which are
generated on-the-fly. This is the class for the dynamic type of
replica.
When the connection to the Source object is made, the initialization
step passes the current property values (see Replica Initialization).
In a DynamicReplica, the property/signal/slot details are also sent,
allowing the replica object to be created on-the-fly. This can be
conventient in QML or scripting, but has two primary disadvantages.
First, the object is in effect "empty" until it is successfully
initialized by the Source. Second, in C++, calls must be made using
QMetaObject::invokeMethod(), as the moc generated lookup will not be
available.
(emphasis mine)
And in the case of PyQt, it only supports the second method, so you get that warning message indicating possible problems.
I'm working on a GUI application, developed in Python and its UI library : PySide2 (Qt wrapper for Python)
I have a heavy computation function I want to put on another thread in order to not freeze my UI. The Ui should show "Loading" and when the function is over, receive from it it's results and update the UI with it.
I've tried a lot of different codes, a lot of examples are working for others but not me, is it PySide2 fault ? (For example this is almost what I want to do : Updating GUI elements in MultiThreaded PyQT)
My code is :
class OtherThread(QThread):
def __init__(self):
QThread.__init__(self)
def run(self):
print 'Running......'
self.emit(SIGNAL("over(object)"), [(1,2,3), (2,3,4)])
#Slot(object)
def printHey( obj):
print 'Hey, I\'ve got an object ',
print obj
thr = OtherThread()
self.connect(thr,SIGNAL("over(object)"),printHey)
thr.start()
My code is working if I use primitives such as bool or int but not with object. I see 'Running....' but never the rest.
Hope someone can enlighten me
You can't define signals dynamically on a class instance. They have to be defined as class attributes. You should be using the new-style signals and slot syntax.
class OtherThread(QThread):
over = QtCore.Signal(object)
def run(self):
...
self.over.emit([(1,2,3), (2,3,4)])
class MyApp(QtCore.QObject)
def __init__(self):
super(MyApp, self).__init__()
self.thread = OtherThread(self)
self.thread.over.connect(self.on_over)
self.thread.start()
#QtCore.Slot(object)
def on_over(self, value):
print 'Thread Value', value
I don't know the first thing about Qt, but I'm trying to be cheeky and borrow code from elsewhere (http://lateral.netmanagers.com.ar/weblog/posts/BB901.html#disqus_thread). ;)
I have a problem. When I run test() the first time, everything works swimmingly. However, when I run it the second time, I get nasty segfaults. I suspect that the problem is that I'm not ending the qt stuff correctly. What should I change about this program to make it work multiple times? Thanks in advance!
from PyQt4 import QtCore, QtGui, QtWebKit
import logging
logging.basicConfig(level=logging.DEBUG)
class Capturer(object):
"""A class to capture webpages as images"""
def __init__(self, url, filename, app):
self.url = url
self.app = app
self.filename = filename
self.saw_initial_layout = False
self.saw_document_complete = False
def loadFinishedSlot(self):
self.saw_document_complete = True
if self.saw_initial_layout and self.saw_document_complete:
self.doCapture()
def initialLayoutSlot(self):
self.saw_initial_layout = True
if self.saw_initial_layout and self.saw_document_complete:
self.doCapture()
def capture(self):
"""Captures url as an image to the file specified"""
self.wb = QtWebKit.QWebPage()
self.wb.mainFrame().setScrollBarPolicy(
QtCore.Qt.Horizontal, QtCore.Qt.ScrollBarAlwaysOff)
self.wb.mainFrame().setScrollBarPolicy(
QtCore.Qt.Vertical, QtCore.Qt.ScrollBarAlwaysOff)
self.wb.loadFinished.connect(self.loadFinishedSlot)
self.wb.mainFrame().initialLayoutCompleted.connect(
self.initialLayoutSlot)
logging.debug("Load %s", self.url)
self.wb.mainFrame().load(QtCore.QUrl(self.url))
def doCapture(self):
logging.debug("Beginning capture")
self.wb.setViewportSize(self.wb.mainFrame().contentsSize())
img = QtGui.QImage(self.wb.viewportSize(), QtGui.QImage.Format_ARGB32)
painter = QtGui.QPainter(img)
self.wb.mainFrame().render(painter)
painter.end()
img.save(self.filename)
self.app.quit()
def test():
"""Run a simple capture"""
app = QtGui.QApplication([])
c = Capturer("http://www.google.com", "google.png", app)
c.capture()
logging.debug("About to run exec_")
app.exec_()
DEBUG:root:Load http://www.google.com
QObject::connect: Cannot connect (null)::configurationAdded(QNetworkConfiguration) to QNetworkConfigurationManager::configurationAdded(QNetworkConfiguration)
QObject::connect: Cannot connect (null)::configurationRemoved(QNetworkConfiguration) to QNetworkConfigurationManager::configurationRemoved(QNetworkConfiguration)
QObject::connect: Cannot connect (null)::configurationUpdateComplete() to QNetworkConfigurationManager::updateCompleted()
QObject::connect: Cannot connect (null)::onlineStateChanged(bool) to QNetworkConfigurationManager::onlineStateChanged(bool)
QObject::connect: Cannot connect (null)::configurationChanged(QNetworkConfiguration) to QNetworkConfigurationManager::configurationChanged(QNetworkConfiguration)
Process Python segmentation fault (this last line is comes from emacs)
You need to handle the QApplication outside of the test functions, sort of like a singleton (it's actually appropriate here).
What you can do is to check if QtCore.qApp is something (or if QApplication.instance() returns None or something else) and only then create your qApp, otherwise, use the global one.
It will not be destroyed after your test() function since PyQt stores the app somewhere.
If you want to be sure it's handled correctly, just setup a lazily initialized singleton for it.
A QApplication should only be initialized once!
It can be used by as many Capture instances as you like, but you should start them in the mainloop.
See: https://doc.qt.io/qt-4.8/qapplication.html
You could also try "del app" after "app.exec_", but I am unsure about the results.
(Your original code runs fine on my system)
I would use urllib instead of webkit:
import urllib
class Capturer:
def capture(self, s_url, s_filename):
s_file_out, httpmessage = urllib.urlretrieve(s_url, s_filename, self.report)
def report(self, i_count, i_chunk, i_size):
print('retrived %5d of %5d bytes' % (i_count * i_chunk, i_size))
def test():
c = Capturer()
c.capture("http://www.google.com/google.png", "google1.png")
c.capture("http://www.google.com/google.png", "google2.png")
if __name__ == '__main__':
test()
I have an object that should signal that a value has changed by emitting a signal with the new value as an argument. The type of the value can change, and so I'm unsure of how to write the signal type. I know that I can acconmplish this using old-style signals like this:
self.emit(SIGNAL("valueChanged(PyQt_PyObject)"), newvalue)
but how would I write this using new-style signals?
I am aware of a previous question related to this but no "real" answer was given there.
First, the object you're emitting from needs the signal defined as an attribute of its class:
class SomeClass(QObject):
valueChanged = pyqtSignal(object)
Notice the signal has one argument of type object, which should allow anything to pass through. Then, you should be able to emit the signal from within the class using an argument of any data type:
self.valueChanged.emit(anyObject)
I'm a beginner and this is the first question I'm attempting to answer, so apologies if I have misunderstood the question...
The following code emits a signal that sends a custom Python object, and the slot uses that
class to print "Hello World".
import sys
from PyQt4.QtCore import pyqtSignal, QObject
class NativePythonObject(object):
def __init__(self, message):
self.message = message
def printMessage(self):
print(self.message)
sys.exit()
class SignalEmitter(QObject):
theSignal = pyqtSignal(NativePythonObject)
def __init__(self, toBeSent, parent=None):
super(SignalEmitter, self).__init__(parent)
self.toBeSent = toBeSent
def emitSignal(self):
self.theSignal.emit(toBeSent)
class ClassWithSlot(object):
def __init__(self, signalEmitter):
self.signalEmitter = signalEmitter
self.signalEmitter.theSignal.connect(self.theSlot)
def theSlot(self, ourNativePythonType):
ourNativePythonType.printMessage()
if __name__ == "__main__":
toBeSent = NativePythonObject("Hello World")
signalEmitter = SignalEmitter(toBeSent)
classWithSlot = ClassWithSlot(signalEmitter)
signalEmitter.emitSignal()