Got "Dynamic metaobject is not assigned" while run QtRemoteObjects - python

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.

Related

PySide / PyQt moving data to threads

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

Attribute passed through signal/slot connexion loses its type

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

Accesing class variable from another script using python

I am facing weird error when I try to access self.cursor_dat' from class in different script. It says:ImportError: cannot import name query_selection_class`. The error also occur without trying to access the variable. There is just something wrong with the import command.
here is the file1.py where I create the variable:
class connection_settings_class(QtGui.QMainWindow,Ui_main_connection_settings_window):
def __init__(self):
self.create_connection_window()
self.host = 'localhost'
self.port = '3307'
self.user = 'root'
self.password = ''
self.database = 'rtr'
def connection(self):
""" connect to the database and create cursor that will be used to exetute MySQL queries """
try:
self.cnxn = pyodbc.connect(driver = '{MySQL ODBC 5.3 ANSI Driver}', # ANSI or Unicode
host = self.host,
port = self.port,
user = self.user,
password = self.password,
database = self.database)
except:
print('Connection FAIL')
**self.cursor_dat** = self.cnxn.cursor()
**self.cursor_dat**.execute("""SELECT * FROM test_db.attempt;""")
row = **self.cursor_dat**.fetchone()
if row:
print("Succesfully connected to the database.")
print row
self.status_label.setText("Connected")
else:
print("Connection FAIL")
self.status_label.setText("Disconnected")
def create_connection_window(self):
...rest of the code
...and here is file2
import file1 -> I also tried from file1 import connection_settings_class
class plausible_implausible_class(QtGui.QMainWindow,Ui_plausible_implausible_win):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setupUi(self)
self.show()
sc = MyStaticMplCanvas(self.centralwidget, width=500, height=400, dpi=100)
self.verticalLayout_3.addWidget(sc)
**a = file1.connection_settings_class.cursor_dat**
I might be making a really dumb mistake looking at your sample, but what I see is this:
self.cursor_dat= self.cnxn.cursor()
is defined in the connection_settings_class.connection() method. You never call connection(), so that property never gets created.
Also I noticed that you're calling the class statically, rather than creating an instance of the class but you've created no static properties, they're all created on __init__. You may want to create an instance of your class, which then defines the cursor_dat to be None initially.
If you want connection sharing you can set up a pool of connections and still create individual instances.
Ok, there is not problem with the path because all the scripts that #tdelaney mentioned were positive. The result of for cycle is '/Users/BauchMAC/PycharmProjects/py_GUI/Database_GUI' --> True. And the working directory which I got using os.getcwd() is same.
I also tried to create two new scripts and everything worked. So the problem is that I don't understand to "importing rules" in python because this is the source of the problem.
So there is obviously problem with crossing of imports. And the way to solve it is by passing the value as the parameter.
Below you can see how I wanted to do it and the reason for the error:
file1:
from try4 import ClassB
class ClassA():
def __init__(self):
self.cursor_dat = 1
self.query_selection_win = ClassB()
object1 = ClassA()
file 2:
from try3 import ClassA <-- source of the probles
class ClassB():
def __init__(self, cursor_dat):
print(ClassA.cursor_dat)
And here is how it should be (at least it works, I am not sure if it is the best way how to do it, I am still newbie)
file 1:
from try4 import ClassB
class ClassA():
def __init__(self):
self.cursor_dat = 1
self.query_selection_win = ClassB(self.cursor_dat)
object1 = ClassA()
file 2:
class ClassB():
def __init__(self, cursor_dat):
print(cursor_dat)
I hope it will be helpful to some newbie like me :)

How do I shut down PyQt's QtApplication correctly?

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()

PyQt signal with arguments of arbitrary type / PyQt_PyObject equivalent for new-style signals

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()

Categories