Skip to content

Commit

Permalink
added support for kwargs by supporting QVariantMap kwargs as last arg…
Browse files Browse the repository at this point in the history
…ument, refactored from forum
  • Loading branch information
florianlink committed Oct 18, 2017
1 parent f3c4f50 commit 9eeab0e
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 74 deletions.
130 changes: 88 additions & 42 deletions src/PythonQtSlot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,68 +341,114 @@ PyObject *PythonQtMemberFunction_Call(PythonQtSlotInfo* info, PyObject* m_self,

PyObject *PythonQtSlotFunction_CallImpl(PythonQtClassInfo* classInfo, QObject* objectToCall, PythonQtSlotInfo* info, PyObject *args, PyObject * kw, void* firstArg, void** directReturnValuePointer, PythonQtPassThisOwnershipType* passThisOwnershipToCPP)
{
if (kw != NULL && PyDict_Check(kw) && (PyDict_Size(kw) > 0)) {
QString e = QString("Calling C++ functions with Python keywords is not supported! Function: ") + info->fullSignature(true) + " Keywords: " + PythonQtConv::PyObjGetString(kw);
PyErr_SetString(PyExc_ValueError, e.toLatin1().data());
return NULL;
}

int argc = args?PyTuple_Size(args):0;

if (passThisOwnershipToCPP) {
*passThisOwnershipToCPP = IgnoreOwnership;
}

#ifdef PYTHONQT_DEBUG
std::cout << "called " << info->metaMethod()->typeName() << " " << info->signature() << std::endl;
#endif

PyObject* r = NULL;
bool ok = false;

if (directReturnValuePointer) {
*directReturnValuePointer = NULL;
}
if (info->nextInfo()) {
// overloaded slot call, try on all slots with strict conversion first
bool strict = true;
PythonQtSlotInfo* i = info;
while (i) {
bool skipFirst = i->isInstanceDecorator();
if (i->parameterCount()-1-(skipFirst?1:0) == argc) {
PyErr_Clear();
ok = PythonQtCallSlot(classInfo, objectToCall, args, strict, i, firstArg, &r, directReturnValuePointer, passThisOwnershipToCPP);
if (PyErr_Occurred() || ok) break;

if( (kw != NULL && PyDict_Check(kw) && (PyDict_Size(kw) > 0)) ) {
// -------------------keyword args slot call -------------------------

// keyword arguments are given as dict, must be mapped to arguments in correct order
// very complicated, so call them only on a slot with last variable name kwargs
// slot must be implemented as
// <type> <name>(any number of positional arguments, QVariantMap kwargs)
int numCombinedArgs = argc + 1;
PyObject* combinedArgs = PyTuple_New(numCombinedArgs);

for (int i = 0; i<argc; i++) {
PyObject* p = PyTuple_GetItem(args,i);
Py_INCREF(p);
PyTuple_SetItem(combinedArgs,i,p);
}

Py_INCREF(kw);
PyTuple_SetItem(combinedArgs, numCombinedArgs - 1, kw);

bool kwSlotFound = false;

QList<QByteArray> parameterNames;
PythonQtSlotInfo* slotInfo = info;
static QByteArray kwargs = "kwargs";
while (slotInfo) {
parameterNames = slotInfo->metaMethod()->parameterNames();
if (!parameterNames.isEmpty() && (parameterNames.last().constData() == kwargs)) {
kwSlotFound = true;
break;
}
i = i->nextInfo();
if (!i) {
if (strict) {
// one more run without being strict
strict = false;
i = info;
}
}
if (kwSlotFound) {
#ifdef PYTHONQT_DEBUG
std::cout << "called " << slotInfo->metaMethod()->typeName() << " " << slotInfo->signature().constData() << std::endl;
#endif

ok = PythonQtCallSlot(classInfo, objectToCall, combinedArgs, false, slotInfo, firstArg, &r, directReturnValuePointer, passThisOwnershipToCPP);
if (!ok && !PyErr_Occurred()) {
QString e = QString("Called ") + info->fullSignature() + " with wrong arguments: " + PythonQtConv::PyObjGetString(args);
PyErr_SetString(PyExc_ValueError, e.toLatin1().data());
}
} else {
QString e = QString("Called ") + info->fullSignature() + " with keyword arguments, but called slot does not support kwargs.";
PyErr_SetString(PyExc_ValueError, e.toLatin1().data());
}
if (!ok && !PyErr_Occurred()) {
QString e = QString("Could not find matching overload for given arguments:\n" + PythonQtConv::PyObjGetString(args) + "\n The following slots are available:\n");

Py_DECREF(combinedArgs);
} else {
// -------------------Normal slot call -------------------------
if (info->nextInfo()) {
// overloaded slot call, try on all slots with strict conversion first
bool strict = true;
PythonQtSlotInfo* i = info;
while (i) {
e += QString(i->fullSignature()) + "\n";
bool skipFirst = i->isInstanceDecorator();
if (i->parameterCount()-1-(skipFirst?1:0) == argc) {
PyErr_Clear();
ok = PythonQtCallSlot(classInfo, objectToCall, args, strict, i, firstArg, &r, directReturnValuePointer, passThisOwnershipToCPP);
if (PyErr_Occurred() || ok) break;
}
i = i->nextInfo();
if (!i) {
if (strict) {
// one more run without being strict
strict = false;
i = info;
}
}
}
PyErr_SetString(PyExc_ValueError, e.toLatin1().data());
}
} else {
// simple (non-overloaded) slot call
bool skipFirst = info->isInstanceDecorator();
if (info->parameterCount()-1-(skipFirst?1:0) == argc) {
PyErr_Clear();
ok = PythonQtCallSlot(classInfo, objectToCall, args, false, info, firstArg, &r, directReturnValuePointer, passThisOwnershipToCPP);
if (!ok && !PyErr_Occurred()) {
QString e = QString("Called ") + info->fullSignature() + " with wrong arguments: " + PythonQtConv::PyObjGetString(args);
QString e = QString("Could not find matching overload for given arguments:\n" + PythonQtConv::PyObjGetString(args) + "\n The following slots are available:\n");
PythonQtSlotInfo* i = info;
while (i) {
e += QString(i->fullSignature()) + "\n";
i = i->nextInfo();
}
PyErr_SetString(PyExc_ValueError, e.toLatin1().data());
}
} else {
QString e = QString("Called ") + info->fullSignature() + " with wrong number of arguments: " + PythonQtConv::PyObjGetString(args);
PyErr_SetString(PyExc_ValueError, e.toLatin1().data());
// simple (non-overloaded) slot call
bool skipFirst = info->isInstanceDecorator();
if (info->parameterCount()-1-(skipFirst?1:0) == argc) {
PyErr_Clear();
#ifdef PYTHONQT_DEBUG
std::cout << "called " << info->metaMethod()->typeName() << " " << info->signature().constData() << std::endl;
#endif
ok = PythonQtCallSlot(classInfo, objectToCall, args, false, info, firstArg, &r, directReturnValuePointer, passThisOwnershipToCPP);
if (!ok && !PyErr_Occurred()) {
QString e = QString("Called ") + info->fullSignature() + " with wrong arguments: " + PythonQtConv::PyObjGetString(args);
PyErr_SetString(PyExc_ValueError, e.toLatin1().data());
}
} else {
QString e = QString("Called ") + info->fullSignature() + " with wrong number of arguments: " + PythonQtConv::PyObjGetString(args);
PyErr_SetString(PyExc_ValueError, e.toLatin1().data());
}
}
}

Expand Down Expand Up @@ -734,7 +780,7 @@ static PyObject*
meth_richcompare(PythonQtSlotFunctionObject *a, PythonQtSlotFunctionObject *b, int op)
{
int x = meth_compare(a, b);
bool r;
bool r = false;
if (op == Py_LT)
r = x < 0;
else if (op == Py_LE)
Expand Down
24 changes: 16 additions & 8 deletions tests/PythonQtTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ void PythonQtTestSlotCalling::testOverloadedCall()
QVERIFY(_helper->runScript("obj.overload(12,13); obj.setPassed();\n", 6));
}


void PythonQtTestSlotCalling::testKeywordCall()
{
QVERIFY(_helper->runScript("if obj.keywordInt(5,value=6)==11: obj.setPassed();\n"));
QVERIFY(_helper->runScript("if obj.keywordOnly(value=6)==1: obj.setPassed();\n"));
QVERIFY(_helper->runScript("if obj.keywordOnly(arg1='test1',arg2='test2')==2: obj.setPassed();\n"));
}

void PythonQtTestSlotCalling::testPyObjectSlotCall()
{
QVERIFY(_helper->runScript("if obj.getPyObject(PythonQt)==PythonQt: obj.setPassed();\n"));
Expand Down Expand Up @@ -300,19 +308,19 @@ void PythonQtTestSlotCalling::testCppFactory()

}

PQCppObject2Decorator::TestEnumFlag PQCppObject2Decorator::testEnumFlag1(PQCppObject2* obj, PQCppObject2Decorator::TestEnumFlag flag) {
PQCppObject2Decorator::TestEnumFlag PQCppObject2Decorator::testEnumFlag1(PQCppObject2* /*obj*/, PQCppObject2Decorator::TestEnumFlag flag) {
return flag;
}

PQCppObject2::TestEnumFlag PQCppObject2Decorator::testEnumFlag2(PQCppObject2* obj, PQCppObject2::TestEnumFlag flag) {
PQCppObject2::TestEnumFlag PQCppObject2Decorator::testEnumFlag2(PQCppObject2* /*obj*/, PQCppObject2::TestEnumFlag flag) {
return flag;
}

// with int overload
PQCppObject2Decorator::TestEnumFlag PQCppObject2Decorator::testEnumFlag3(PQCppObject2* obj, int flag) {
PQCppObject2Decorator::TestEnumFlag PQCppObject2Decorator::testEnumFlag3(PQCppObject2* /*obj*/, int /*flag*/) {
return (TestEnumFlag)-1;
}
PQCppObject2Decorator::TestEnumFlag PQCppObject2Decorator::testEnumFlag3(PQCppObject2* obj, PQCppObject2Decorator::TestEnumFlag flag) {
PQCppObject2Decorator::TestEnumFlag PQCppObject2Decorator::testEnumFlag3(PQCppObject2* /*obj*/, PQCppObject2Decorator::TestEnumFlag flag) {
return flag;
}

Expand Down Expand Up @@ -583,25 +591,25 @@ void PythonQtTestApi::testQColorDecorators()
QVERIFY(colorClass.call("red", QVariantList() << QColor(255,0,0)).toInt() == 255);
}

QByteArray PythonQtTestApiHelper::readFileAsBytes(const QString& filename)
QByteArray PythonQtTestApiHelper::readFileAsBytes(const QString& /*filename*/)
{
QByteArray b;
return b;
}

QByteArray PythonQtTestApiHelper::readSourceFile(const QString& filename, bool& ok)
QByteArray PythonQtTestApiHelper::readSourceFile(const QString& /*filename*/, bool& ok)
{
QByteArray b;
ok = true;
return b;
}

bool PythonQtTestApiHelper::exists(const QString& filename)
bool PythonQtTestApiHelper::exists(const QString& /*filename*/)
{
return true;
}

QDateTime PythonQtTestApiHelper::lastModifiedDate(const QString& filename) {
QDateTime PythonQtTestApiHelper::lastModifiedDate(const QString& /*filename*/) {
return QDateTime::currentDateTime();
}

Expand Down
53 changes: 29 additions & 24 deletions tests/PythonQtTests.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,10 @@ public Q_SLOTS:
PQCppObjectNoWrap* new_PQCppObjectNoWrap() {
return new PQCppObjectNoWrap(0);
}
PQCppObjectNoWrap* new_PQCppObjectNoWrap(const PQCppObjectNoWrap& other) {
PQCppObjectNoWrap* new_PQCppObjectNoWrap(const PQCppObjectNoWrap& /*other*/) {
return new PQCppObjectNoWrap(1);
}
PQCppObjectNoWrap* new_PQCppObjectNoWrap(double value) {
PQCppObjectNoWrap* new_PQCppObjectNoWrap(double /*value*/) {
return new PQCppObjectNoWrap(2);
}

Expand Down Expand Up @@ -324,6 +324,7 @@ private Q_SLOTS:
void testMultiArgsSlotCall();
void testPyObjectSlotCall();
void testOverloadedCall();
void testKeywordCall();
void testCppFactory();
void testInheritance();
void testAutoConversion();
Expand Down Expand Up @@ -399,13 +400,17 @@ public Q_SLOTS:
void testNoArg() { _called = true; }

//! overload test!
void overload(bool a) { _calledOverload = 0; _called = true; }
void overload(float a) { _calledOverload = 1; _called = true;}
void overload(int a) { _calledOverload = 2; _called = true;}
void overload(const QString& str) { _calledOverload = 3; _called = true;}
void overload(const QStringList& str) { _calledOverload = 4; _called = true;}
void overload(QObject* str) { _calledOverload = 5; _called = true;}
void overload(float a, int b) { _calledOverload = 6; _called = true;}
void overload(bool /*a*/) { _calledOverload = 0; _called = true; }
void overload(float /*a*/) { _calledOverload = 1; _called = true;}
void overload(int /*a*/) { _calledOverload = 2; _called = true;}
void overload(const QString& /*str*/) { _calledOverload = 3; _called = true;}
void overload(const QStringList& /*str*/) { _calledOverload = 4; _called = true;}
void overload(QObject* /*str*/) { _calledOverload = 5; _called = true;}
void overload(float /*a*/, int /*b*/) { _calledOverload = 6; _called = true;}

//!keyword argument tests
int keywordInt(int i, const QVariantMap& kwargs = QVariantMap()) { _called = true; return (i + kwargs["value"].toInt()); }
int keywordOnly(const QVariantMap& kwargs = QVariantMap()) { _called = true; return kwargs.count(); }

//! POD values:
int getInt(int a) { _called = true; return a; }
Expand Down Expand Up @@ -474,8 +479,8 @@ public Q_SLOTS:
// returned object needs to get an extra ref count!
Py_XINCREF(obj);
return obj;
};
QVariant getPyObjectFromVariant2(const QVariant& val) { _called = true; return val; };
}
QVariant getPyObjectFromVariant2(const QVariant& val) { _called = true; return val; }

//! testing pointer passing
PythonQtTestSlotCallingHelper* getTestObject(PythonQtTestSlotCallingHelper* obj) { _called = true; return obj; }
Expand Down Expand Up @@ -522,10 +527,10 @@ public Q_SLOTS:
ClassA* createClassDAsA() { _called = true; return new ClassD; }
ClassB* createClassDAsB() { _called = true; return new ClassD; }

QColor setAutoConvertColor(const QColor& color) { _called = true; return color; };
QBrush setAutoConvertBrush(const QBrush& brush) { _called = true; return brush; };
QPen setAutoConvertPen(const QPen& pen) { _called = true; return pen; };
QCursor setAutoConvertCursor(const QCursor& cursor) { _called = true; return cursor; };
QColor setAutoConvertColor(const QColor& color) { _called = true; return color; }
QBrush setAutoConvertBrush(const QBrush& brush) { _called = true; return brush; }
QPen setAutoConvertPen(const QPen& pen) { _called = true; return pen; }
QCursor setAutoConvertCursor(const QCursor& cursor) { _called = true; return cursor; }

private:
bool _passed;
Expand Down Expand Up @@ -560,24 +565,24 @@ class PythonQtTestSignalHandlerHelper : public QObject
public:
PythonQtTestSignalHandlerHelper(PythonQtTestSignalHandler* test) {
_test = test;
};
}

public Q_SLOTS:
void setPassed() { _passed = true; }

bool emitIntSignal(int a) { _passed = false; emit intSignal(a); return _passed; };
bool emitFloatSignal(float a) { _passed = false; emit floatSignal(a); return _passed; };
bool emitEnumSignal(PQCppObject2::TestEnumFlag flag) { _passed = false; emit enumSignal(flag); return _passed; };
bool emitIntSignal(int a) { _passed = false; emit intSignal(a); return _passed; }
bool emitFloatSignal(float a) { _passed = false; emit floatSignal(a); return _passed; }
bool emitEnumSignal(PQCppObject2::TestEnumFlag flag) { _passed = false; emit enumSignal(flag); return _passed; }

bool emitVariantSignal(const QVariant& v) { _passed = false; emit variantSignal(v); return _passed; };
bool emitVariantSignal(const QVariant& v) { _passed = false; emit variantSignal(v); return _passed; }
QVariant expectedVariant() { return _v; }
void setExpectedVariant(const QVariant& v) { _v = v; }

bool emitComplexSignal(int a, float b, const QStringList& l, QObject* obj) { _passed = false; emit complexSignal(a,b,l,obj); return _passed; };
bool emitComplexSignal(int a, float b, const QStringList& l, QObject* obj) { _passed = false; emit complexSignal(a,b,l,obj); return _passed; }

bool emitSignal1(int a) { _passed = false; emit signal1(a); return _passed; };
bool emitSignal2(const QString& s) { _passed = false; emit signal2(s); return _passed; };
bool emitSignal3(float a) { _passed = false; emit signal3(a); return _passed; };
bool emitSignal1(int a) { _passed = false; emit signal1(a); return _passed; }
bool emitSignal2(const QString& s) { _passed = false; emit signal2(s); return _passed; }
bool emitSignal3(float a) { _passed = false; emit signal3(a); return _passed; }

Q_SIGNALS:
void intSignal(int);
Expand Down

0 comments on commit 9eeab0e

Please sign in to comment.