[PyQt] PyQt 5.7, QSqlQueryModel.data() sub-classed override bug?

J Barchan jnbarchan at gmail.com
Thu May 3 13:55:12 BST 2018


On 3 May 2018 at 13:05, Phil Thompson <phil at riverbankcomputing.com> wrote:

> On 3 May 2018, at 12:25 pm, J Barchan <jnbarchan at gmail.com> wrote:
> >
> > ​​
> > I am finding (in PyQt 5.7 at least) that sub-classing QSqlQueryModel and
> overriding its data() method produces an incorrect result when the value
> retrieved from a MySQL database is NULL.​
> >
> > Full details are in https://forum.qt.io/topic/90363/inexplicable-
> qsqlquerymodel-handling-of-null-value, and particularly post #
> https://forum.qt.io/topic/90363/inexplicable-qsqlquerymodel-handling-of-
> null-value.  Nobody has tried it in C++ for me to date to verify, but I'm
> suspecting this might be a PyQt bug?
> >
> > Briefly:
> > My SELECT query returns a column which is NULLable, and has NULL as its
> value.  Where I expect "blank" as the end value, I actually get, for
> example, 0 if the column type is int or '' if the type is string, etc.
> >
> > This is when I sub-class QSqlQueryModel.  If all I have is:
> > class DBQueryModel(QtSql.QSqlQueryModel):
> >     def __init__(self, parent=None):
> >         super().__init__(parent)
> > I get the "NULL"/"blank".  However, as soon as I add just:
> > def data(self, index: QtCore.QModelIndex, role=QtCore.Qt.DisplayRole) ->
> typing.Any:
> >     return super().data(index, role)
> > I get those values instead of NULL.
> >
> > Note that my override is based on the Qt definition of the method at
> http://doc.qt.io/qt-5/qsqlquerymodel.html#data:
> > QVariant QSqlQueryModel::data(const QModelIndex &item, int role =
> Qt::DisplayRole) const
> >
> > Note that the default for role is Qt::DisplayRole.  However, in QtSql.py
> I see:
> > def data(self, QModelIndex, role=None): # real signature unknown;
> restored from __doc__
> >     """ data(self, QModelIndex, role: int = Qt.DisplayRole) -> Any """
> >     pass
> >  You will notice that the comment shows the default should be
> Qt.DisplayRole, but the declaration defaults it to None instead.
>
> What is QtSql.py?
>
> If you want to know the signature of a method pss it to help().
>
> > I don't know enough to be sure, but would that be the underlying cause
> of the unexpected behaviour?
> >
> > FWIW, I have tried making my override be:
> > def data(self, index: QtCore.QModelIndex, role=None)
> > instead, but same bad behaviour.
> >
> > 1. Is this indeed a bug in PyQt, and the cause of my issue?
>
> No and no.
>
> > 2. If so, I presume you (Phil!) will be kind enough to fix.  However,
> for my part I am stuck with PyQt 5.7 for the foreseeable future.  If the
> fix is indeed to change code in the latest/next release, is there anything
> I can do in existing code (my override) to make it work in 5.7, as a
> workaround?  (in real code I need the override, as I do other processing)
> >
> > My coding has come to halt as I cannot proceed without a fix.  So I
> should be obliged for any early response as to whether this is the cause of
> my woes.  I do realise PyQt support/fixes are quite voluntary, and so thank
> whoever in advance!
>
> Phil


​For QtSql.py:​

​Hmm​, I had not realised.  I use PyCharm as my IDE.  From there, while I
am coding, I can click on anything PyQt and ask for "Go to
definition/declaration".  The editor then 9in this case) opens me up into a
file named QtSql.py, showing me in this case [extract]:

class QSqlQueryModel(__PyQt5_QtCore.QAbstractTableModel):
>
> ...
>
> def columnCount(self, parent=None, *args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__
>     """ columnCount(self, parent: QModelIndex = QModelIndex()) -> int """
>     pass
>
> def data(self, QModelIndex, role=None): # real signature unknown; restored from __doc__
>     """ data(self, QModelIndex, role: int = Qt.DisplayRole) -> Any """
>     pass
>
> def endInsertColumns(self): # real signature unknown; restored from __doc__
>     """ endInsertColumns(self) """
>     pass
>
>
etc.  I had *assumed* this was a file supplied with PyQt.  I guess now it's
"generated on the fly" by PyCharm (I see its path is in a PyCharm temporary
directory).  At other times, it might open, say,
/usr/lib/python3/dist-packages/PyQt5/QtCore.pyi, which I think is a file
you supply.  Oh, at the head of this QtSql.py I see:

# encoding: utf-8
# module PyQt5.QtSql
# from /usr/lib/python3/dist-packages/PyQt5/QtSql.cpython-35m-x86_64-linux-gnu.so
# by generator 1.145
# no doc


​You'll probably understand all this better than I!​



​For my problem:​

I think I now understand better why it's not a PyQt method definition issue.

However, from the linked Qt forum discussion, I'm stuck between a rock & a
hard place, because the only help I'm getting is that it might be a PyQt
issue.  I do not have C++ to try that out.  So, I wonder if I might ask you
if you can make any suggestion as to the cause, even if it is not a PyQt
issue, given that you are familiar with Qt at least?

To summarise my problem as briefly as possible:

1.
I start with:

model = QtSql.QSqlQueryModel(self)
model.setQuery("SELECT LandlordNo, SMTPAccountId FROM landlords WHERE
SMTPAccountId IS NULL")

# or plain "SELECT NULL AS SMTPAccountId", to eliminate anything about
the column definition being an issue

rowCount = model.rowCount()
if rowCount > 0:
    rec = model.record(0)
    field = rec.field("SMTPAccountId")
    isn = field.isNull()

SMTPAccointId returns NULL from MySQL.  *At this point field.isNull()
correctly returns True.*

2.
I sub-class QSqlQueryModel, and use that, with quite simply, exactly:

class DBQueryModel(QtSql.QSqlQueryModel):
    def __init__(self, parent=None):
        super().__init__(parent)

and use that sub-class in place of QSqlQueryModel:   model = DBQueryModel(
self)
​*And it this point ​point field.isNull() *still* correctly returns True.*

3.
Then I add *just exactly this* to my sub-class:

def data(self, index: QtCore.QModelIndex, role=QtCore.Qt.DisplayRole)
-> typing.Any:
    return super().data(index, role)

(I've also tried role=None) You can see that simply calls the base class
method.  *But now field.isNull() returns False!!*  The application sees 0
instead of NULL for the value (SMTPAccountId is declared INT NULL), or ''
if I use SELECT NULL AS SMTPAccountId so it counts as string.


This leaves me completely stumped.  I have no idea where the problem is
(there shouldn't be a problem!).  I have to sub-class the data() method for
other purposes, but then it handles NULL (only) incorrectly.

Would you have any idea what is going on here?  My thanks in advance.


-- 
Kindest,
Jonathan
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20180503/3c068372/attachment-0001.html>


More information about the PyQt mailing list