Search code examples
pythonpyqtpyqt4python-sip

Cannot wrap QWebView.load method


I believe it is common practice (at least it is for me) to be able to wrap function calls.

For example, as an example, a minimilistic wrapping function looks like:

def wrap(fn, *args, **kwargs):
    return fn(*args, **kwargs)

And you could call an arbitrary method via

wrap(qt_method, 1, 2, foo='bar')

which would be equivalent to directly calling

qt_method(1,2, foo='bar')

This generally works for me. However, I've come across a case where it doesn't.

QWebView.load() doesn't seem to like having an empty dictionary expanded into its call signature. E.g. wrap(my_webview.load, QUrl('http://istonyabbottstillprimeminister.com')) fails with the exception: TypeError: QWebView.load(QUrl): argument 1 has unexpected type 'QUrl'.

Below is a minimilistic working example that demonstrates things that work, and don't. I have yet to find another Qt method that fails to wrap like this.

import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtWebKit import QWebView

qapplication = QtGui.QApplication(sys.argv)

webview = QWebView()

url = QtCore.QUrl('http://istonyabbottstillprimeminister.com')

# basic wrapping function
def wraps(fn, *args, **kwargs):
    return fn(*args, **kwargs)

args = ()
kwargs = {}

wraps(webview.load, url)     # Doesn't work
webview.load(url, **kwargs)  # Doesn't work
webview.load(url)            # works
webview.url(*args, **kwargs) # works

webview.show()
qapplication.exec_()

This problem also applies when you subclass QWebView and override the load method like this:

def load(self *args, **kwargs):
    return QWebView.load(self, *args, **kwargs)

Of course, if you instead call QWebView.load(self, *args) or do not use *args, **kwargs in the methods signature, then you don't get the exception (which follows what is seen from the minimilistic working example ebove)

Any insight into this would be appreciated.


Solution

  • I'm unable to reproduce this except with overloaded PyQt methods that have a specific combination of call signatures. Only when one signature has a single argument, and another has a single argument and the rest keyword arguments with default values, does this bug seem to be hit.

    There are exactly nine such methods in PyQt4:

    QtNetwork
      QSslSocket
        addDefaultCaCertificates(QString, QSsl.EncodingFormat format=QSsl.Pem, QRegExp.PatternSyntax syntax=QRegExp.FixedString)
        addDefaultCaCertificates(list-of-QSslCertificate)
    
        addCaCertificates(QString, QSsl.EncodingFormat format=QSsl.Pem, QRegExp.PatternSyntax syntax=QRegExp.FixedString)
        addCaCertificates(list-of-QSslCertificate)
    
        setPrivateKey(QSslKey)
        setPrivateKey(QString, QSsl.KeyAlgorithm algorithm=QSsl.Rsa, QSsl.EncodingFormat format=QSsl.Pem, QByteArray passPhrase=QByteArray())
    
        setLocalCertificate(QSslCertificate)
        setLocalCertificate(QString, QSsl.EncodingFormat format=QSsl.Pem)
    
    QtWebKit
      QWebFrame
        load(QUrl)
        load(QNetworkRequest, QNetworkAccessManager.Operation operation=QNetworkAccessManager.GetOperation, QByteArray body=QByteArray())
    
      QGraphicsWebView
        load(QUrl)
        load(QNetworkRequest, QNetworkAccessManager.Operation operation=QNetworkAccessManager.GetOperation, QByteArray body=QByteArray())
    
      QWebView
        load(QUrl)
        load(QNetworkRequest, QNetworkAccessManager.Operation operation=QNetworkAccessManager.GetOperation, QByteArray body=QByteArray())
    
    QtGui
      QGraphicsScene
        items(Qt.SortOrder)
        QGraphicsScene.items(QPointF)
        QGraphicsScene.items(QRectF, Qt.ItemSelectionMode mode=Qt.IntersectsItemShape)
        QGraphicsScene.items(QPolygonF, Qt.ItemSelectionMode mode=Qt.IntersectsItemShape)
        QGraphicsScene.items(QPainterPath, Qt.ItemSelectionMode mode=Qt.IntersectsItemShape)
    
      QGraphicsView
        items(QPoint)
        QGraphicsView.items(QRect, Qt.ItemSelectionMode mode=Qt.IntersectsItemShape)
        QGraphicsView.items(QPolygon, Qt.ItemSelectionMode mode=Qt.IntersectsItemShape)
        QGraphicsView.items(QPainterPath, Qt.ItemSelectionMode mode=Qt.IntersectsItemShape)
    

    and you just happened to hit on one. I would report this as a bug against SIP. Its heuristics for figuring out which method to call seem in general very good, and it happens to be failing just for this particular combination or call signatures. If you want to maximise confidence that things won't break when fed arbitrary functions that might be parsing their arguments poorly, I'd use something like this your code:

    def call_method_considerately(method, *args, **kwargs):
        if args and kwargs:
            return method(*args, **kwargs)
        elif args:
            return method(*args)
        elif kwargs:
            return method(**kwargs)
        else:
            return method()