How can I bind the SQLite driver notification signal to include the notification source and payload in a PyQt5 project as seen in the C++ docs from QT? As far as I can see overloading the signal with the driver[str].connect syntax will only accept one argument.
connect(sqlDriver, QOverload<const QString &, QSqlDriver::NotificationSource, const QVariant &>::of(&QSqlDriver::notification),
[=](const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload){ /* ... */ });
Can be found at https://doc.qt.io/qt-5/qsqldriver.html#notification-1
Minimal example:
import sys
from PyQt5 import QtSql, QtGui, QtWidgets
def createDB():
db = QtSql.QSqlDatabase.addDatabase('QSQLITE')
db.setDatabaseName(':memory:')
if not db.open():
print('could not open')
return False
query = QtSql.QSqlQuery()
query.exec_("create table sportsmen(id int primary key, "
"firstname varchar(20), lastname varchar(20))")
query.exec_("insert into sportsmen values(101, 'Roger', 'Federer')")
query.exec_("insert into sportsmen values(102, 'Christiano', 'Ronaldo')")
query.exec_("insert into sportsmen values(103, 'Ussain', 'Bolt')")
query.exec_("insert into sportsmen values(104, 'Sachin', 'Tendulkar')")
query.exec_("insert into sportsmen values(105, 'Saina', 'Nehwal')")
return db
class Table(QtSql.QSqlTableModel):
def __init__(self):
super(Table, self).__init__()
self.setTable('sportsmen')
self.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange)
self.database().driver().subscribeToNotification('sportsmen')
self.database().driver().notification[str].connect(self.onNotification)
# signal = pyqtSignal([str], [QSqlDriver.NotificationSource], ['QVariant'])
# @pyqtSlot(str, int, str)
def onNotification(self, name, source=None, payout=None):
"""
Only sends the name of the signal, need to bind the full; name, source, paylad signal
"""
print(self.sender)
print(name, source, payout)
print('it worked')
def addrow():
print(model.rowCount())
record = model.record()
model.insertRecord(-1, record)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
db = createDB()
model = Table()
view = QtWidgets.QTableView()
view.setModel(model)
model.select()
dlg = QtWidgets.QDialog()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(view)
button = QtWidgets.QPushButton("Add a row")
button.clicked.connect(addrow)
layout.addWidget(button)
btn1 = QtWidgets.QPushButton("del a row")
btn1.clicked.connect(lambda: model.removeRow(view.currentIndex().row()))
layout.addWidget(btn1)
dlg.setLayout(layout)
dlg.show()
sys.exit(app.exec_())
Answer
To bind the SQLite driver notification signal in PyQt5 and include the notification source and payload, you can use the signal syntax in PyQt5 and explicitly declare a custom slot that can accept multiple parameters.
Problem Breakdown:
In the original C++ example from Qt, the signal for the notification
method is overloaded to accept multiple parameters:
QOverload<const QString &, QSqlDriver::NotificationSource, const QVariant &>::of(&QSqlDriver::notification)
In PyQt5, while you can directly connect to a signal with a single argument, you will need to handle multiple parameters (in this case, the QString
, NotificationSource
, and QVariant
) by defining the correct slot signature.
Solution:
To handle the signal with multiple parameters, you can define a slot in PyQt5 with the appropriate argument types and connect the signal from the database driver to this slot.
Code Implementation:
Here is an updated version of your code that correctly connects to the notification
signal from the QSqlDriver
and includes the full name, source, and payload in the onNotification
method:
import sys
from PyQt5 import QtSql, QtGui, QtWidgets
from PyQt5.QtCore import pyqtSignal, pyqtSlot
def createDB():
db = QtSql.QSqlDatabase.addDatabase('QSQLITE')
db.setDatabaseName(':memory:')
if not db.open():
print('could not open')
return False
query = QtSql.QSqlQuery()
query.exec_("CREATE TABLE sportsmen(id INT PRIMARY KEY, "
"firstname VARCHAR(20), lastname VARCHAR(20))")
query.exec_("INSERT INTO sportsmen VALUES(101, 'Roger', 'Federer')")
query.exec_("INSERT INTO sportsmen VALUES(102, 'Cristiano', 'Ronaldo')")
query.exec_("INSERT INTO sportsmen VALUES(103, 'Usain', 'Bolt')")
query.exec_("INSERT INTO sportsmen VALUES(104, 'Sachin', 'Tendulkar')")
query.exec_("INSERT INTO sportsmen VALUES(105, 'Saina', 'Nehwal')")
return db
class Table(QtSql.QSqlTableModel):
def __init__(self):
super(Table, self).__init__()
self.setTable('sportsmen')
self.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange)
# Subscribe to notifications for changes in the sportsmen table
self.database().driver().subscribeToNotification('sportsmen')
# Connect to the notification signal and specify the slot to call
self.database().driver().notification[str].connect(self.onNotification)
@pyqtSlot(str, object, object)
def onNotification(self, name, source, payload):
"""
Slot to handle the notification signal with parameters: name, source, and payload
"""
print(f"Notification received from: {name}")
print(f"Source: {source}")
print(f"Payload: {payload}")
print('Notification handled successfully')
def addrow():
print(model.rowCount())
record = model.record()
model.insertRecord(-1, record)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
# Create and open the database in memory
db = createDB()
# Create and set up the model for the QTableView
model = Table()
view = QtWidgets.QTableView()
view.setModel(model)
model.select()
dlg = QtWidgets.QDialog()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(view)
# Add button to insert a row
button = QtWidgets.QPushButton("Add a row")
button.clicked.connect(addrow)
layout.addWidget(button)
# Add button to delete a row
btn1 = QtWidgets.QPushButton("Delete a row")
btn1.clicked.connect(lambda: model.removeRow(view.currentIndex().row()))
layout.addWidget(btn1)
dlg.setLayout(layout)
dlg.show()
sys.exit(app.exec_())
Key Changes:
-
Signal Connection:
- We are connecting to the
notification
signal, which emits astr
,NotificationSource
, andQVariant
(payload). - The signal connection is defined as
self.database().driver().notification[str].connect(self.onNotification)
, whereself.onNotification
is the method that will handle the signal.
- We are connecting to the
-
Slot Definition:
- The method
onNotification
is decorated with@pyqtSlot(str, object, object)
, which indicates that the method expects three parameters:name
(a string),source
(an object, which is typically of typeQSqlDriver.NotificationSource
), andpayload
(aQVariant
). - This method prints out the received values.
- The method
Explanation:
-
PyQt5 Slot Signature: The
@pyqtSlot(str, object, object)
decorator allows you to define the slot to handle three arguments:str
: This represents the table or notification name.object
: This represents the source of the notification, which is an enum value (QSqlDriver.NotificationSource
).object
: This represents the payload, which is aQVariant
.
-
Signal Connection: The
notification
signal inQSqlDriver
sends these three values, and we handle them in theonNotification
method.
Expected Output:
Whenever there is a change in the sportsmen
table (e.g., when a row is inserted, updated, or deleted), the onNotification
slot will be called, and you should see output like:
Notification received from: sportsmen
Source: <QSqlDriver.NotificationSource object at 0x7fd1c2c1e0b0>
Payload: QVariant(123)
Notification handled successfully
You can then customize the handling of the notification as per your needs, such as updating the UI or taking other actions based on the notification.
Notes:
- Ensure that your SQLite driver supports sending notifications, and that you have subscribed to the correct notifications using
subscribeToNotification('sportsmen')
. - The
NotificationSource
enum is usually associated with the type of change, such asNotificationSource.Transaction
,NotificationSource.Insert
,NotificationSource.Update
, orNotificationSource.Delete
.