How to connect to the QSqlDriver notification signal

ghz 昨天 ⋅ 3 views

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:

  1. Signal Connection:

    • We are connecting to the notification signal, which emits a str, NotificationSource, and QVariant (payload).
    • The signal connection is defined as self.database().driver().notification[str].connect(self.onNotification), where self.onNotification is the method that will handle the signal.
  2. 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 type QSqlDriver.NotificationSource), and payload (a QVariant).
    • This method prints out the received values.

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 a QVariant.
  • Signal Connection: The notification signal in QSqlDriver sends these three values, and we handle them in the onNotification 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 as NotificationSource.Transaction, NotificationSource.Insert, NotificationSource.Update, or NotificationSource.Delete.