Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PyObject wrapper #27

Merged
merged 9 commits into from
Oct 8, 2014
Prev Previous commit
Next Next commit
Calling methods on Python objects.
  • Loading branch information
Dennis Tomas committed Sep 30, 2014
commit ff0d915cf223eb0fa845e67786bb04e06e514399
15 changes: 15 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ imported modules using:
signal is emitted with ``traceback`` containing the exception info
(QML API version 1.2 and newer).

.. function:: callMethod(obj, string method, args=[], function callback(result) {})

Call the Python method ``method`` on object ``obj`` with ``args``
asynchronously.
If ``args`` is omitted, ``method`` will be called without arguments.
If ``callback`` is a callable, it will be called with the Python
method result as single argument when the call has succeeded.

If a JavaScript exception occurs in the callback, the :func:`error`
signal is emitted with ``traceback`` containing the exception info.

For some of these methods, there also exist synchronous variants, but it is
highly recommended to use the asynchronous variants instead to avoid blocking
the QML UI thread:
Expand All @@ -155,6 +166,10 @@ the QML UI thread:

Call a Python function. Returns the return value of the Python function.

.. function:: callMethod_sync(obj, string method, var args=[]) -> var

Call a Python method. Returns the return value of the Python method.

The following functions allow access to the version of the running PyOtherSide
plugin and Python interpreter.

Expand Down
71 changes: 45 additions & 26 deletions src/qpython.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ QPython::QPython(QObject *parent, int api_version_major, int api_version_minor)

QObject::connect(this, SIGNAL(process(QString,QVariant,QJSValue *)),
worker, SLOT(process(QString,QVariant,QJSValue *)));
QObject::connect(this, SIGNAL(processMethod(QVariant,QString,QVariant,QJSValue *)),
worker, SLOT(processMethod(QVariant,QString,QVariant,QJSValue *)));
QObject::connect(worker, SIGNAL(finished(QVariant,QJSValue *)),
this, SLOT(finished(QVariant,QJSValue *)));

Expand Down Expand Up @@ -245,40 +247,57 @@ QPython::call_sync(QString func, QVariant args)
return QVariant();
}

if (PyCallable_Check(callable)) {
QVariant v;

PyObject *argl = convertQVariantToPyObject(args);
if (!PyList_Check(argl)) {
Py_DECREF(callable);
Py_XDECREF(argl);
emit error(QString("Not a parameter list in call to %1: %2")
.arg(func).arg(args.toString()));
priv->leave();
return QVariant();
}
QVariant v;
QString errorMessage = priv->call(callable, func, args, &v);
if (!errorMessage.isNull()) {
emit error(errorMessage);
}
Py_DECREF(callable);
priv->leave();
return v;
}

PyObject *argt = PyList_AsTuple(argl);
Py_DECREF(argl);
PyObject *o = PyObject_Call(callable, argt, NULL);
Py_DECREF(argt);
void
QPython::callMethod(QVariant obj, QString method, QVariant args, QJSValue callback)
{
QJSValue *cb = 0;
if (!callback.isNull() && !callback.isUndefined() && callback.isCallable()) {
cb = new QJSValue(callback);
}
emit processMethod(obj, method, args, cb);
}

if (o == NULL) {
emit error(QString("Return value of PyObject call is NULL: %1").arg(priv->formatExc()));
} else {
v = convertPyObjectToQVariant(o);
Py_DECREF(o);
}
QVariant
QPython::callMethod_sync(QVariant obj, QString method, QVariant args)
{
priv->enter();
PyObject *pyobj = convertQVariantToPyObject(obj);

Py_DECREF(callable);
if (pyobj == NULL) {
emit error(QString("Failed to convert %1 to python object: '%1' (%2)").arg(obj.toString()).arg(priv->formatExc()));
priv->leave();
return v;
return QVariant();
}

emit error(QString("Not a callable: %1").arg(func));
QByteArray byteArray = method.toUtf8();
const char *methodStr = byteArray.data();

PyObject *callable = PyObject_GetAttrString(pyobj, methodStr);

if (callable == NULL) {
emit error(QString("Method not found: '%1' (%2)").arg(method).arg(priv->formatExc()));
priv->leave();
return QVariant();
}

QVariant v;
QString errorMessage = priv->call(callable, method, args, &v);
if (!errorMessage.isNull()) {
emit error(errorMessage);
}
Py_DECREF(callable);
priv->leave();
return QVariant();
return v;
}

void
Expand Down
71 changes: 71 additions & 0 deletions src/qpython.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#ifndef PYOTHERSIDE_QPYTHON_H
#define PYOTHERSIDE_QPYTHON_H

#include "Python.h"

#include <QVariant>
#include <QObject>
#include <QString>
Expand Down Expand Up @@ -239,6 +241,74 @@ class QPython : public QObject {
call_sync(QString func, QVariant args=QVariantList());


/**
* \brief Asynchronously call a Python method
*
* Call a method of a Python object asynchronously and call back
* into QML when the result is available:
*
* \code
* Python {
* Component.onCompleted: {
* importModule('datetime', function() {
* call('datetime.datetime.now', [], function(dt) {
* console.log(dt);
* callMethod(dt, 'strftime', ['%Y-%m-%d'], function(result) {
* console.log(result);
* });
* });
* });
* }
* }
* \endcode
*
* \arg obj The Python object
* \arg method The method to call
* \arg args A list of arguments, or \c [] for no arguments
* \arg callback A callback that receives the function call result
**/
Q_INVOKABLE void
callMethod(
QVariant obj,
QString func,
QVariant args=QVariantList(),
QJSValue callback=QJSValue());


/**
* \brief Synchronously call a Python method
*
* This is the synchronous variant of callMethod(). In general, you
* should use callMethod() instead of this function to avoid blocking
* the QML UI thread. Example usage:
*
* \code
* Python {
* Component.onCompleted: {
* importModule('datetime', function() {
* call('datetime.datetime.now', [], function(dt) {
* console.log(dt);
* console.log(
* callMethod_sync(dt, 'strftime', ['%Y-%m-%d'])
* );
* });
* });
* }
* }
* \endcode
*
* \arg obj The Python object
* \arg method The method to call
* \arg args A list of arguments, or \c [] for no arguments
* \result The return value of the Python call as Qt data type
**/
Q_INVOKABLE QVariant
callMethod_sync(
QVariant obj,
QString func,
QVariant args=QVariantList());


/**
* \brief Get the PyOtherSide version
*
Expand Down Expand Up @@ -280,6 +350,7 @@ class QPython : public QObject {

/* For internal use only */
void process(QString func, QVariant args, QJSValue *callback);
void processMethod(QVariant obj, QString method, QVariant args, QJSValue *callback);
void import(QString name, QJSValue *callback);

private slots:
Expand Down
30 changes: 30 additions & 0 deletions src/qpython_priv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,33 @@ QPythonPriv::importFromQRC(const char *module, const QString &filename)

return QString();
}

QString
QPythonPriv::call(PyObject *callable, QString name, QVariant args, QVariant *v)
{
if (!PyCallable_Check(callable)) {
return QString("Not a callable: %1").arg(name);
}

PyObject *argl = convertQVariantToPyObject(args);
if (!PyList_Check(argl)) {
Py_XDECREF(argl);
return QString("Not a parameter list in call to %1: %2")
.arg(name).arg(args.toString());
}

PyObject *argt = PyList_AsTuple(argl);
Py_DECREF(argl);
PyObject *o = PyObject_Call(callable, argt, NULL);
Py_DECREF(argt);

if (o == NULL) {
return QString("Return value of PyObject call is NULL: %1").arg(priv->formatExc());
} else {
if (v != NULL) {
*v = convertPyObjectToQVariant(o);
}
Py_DECREF(o);
}
return QString();
}
1 change: 1 addition & 0 deletions src/qpython_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class QPythonPriv : public QObject {
void leave();

QString importFromQRC(const char *module, const QString &filename);
QString call(PyObject *callable, QString name, QVariant args, QVariant *v);

void receiveObject(PyObject *o);
static void closing();
Expand Down
9 changes: 9 additions & 0 deletions src/qpython_worker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ QPythonWorker::process(QString func, QVariant args, QJSValue *callback)
}
}

void
QPythonWorker::processMethod(QVariant obj, QString method, QVariant args, QJSValue *callback)
{
QVariant result = qpython->callMethod_sync(obj, method, args);
if (callback) {
emit finished(result, callback);
}
}

void
QPythonWorker::import(QString name, QJSValue *callback)
{
Expand Down
1 change: 1 addition & 0 deletions src/qpython_worker.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class QPythonWorker : public QObject {

public slots:
void process(QString func, QVariant args, QJSValue *callback);
void processMethod(QVariant obj, QString method, QVariant args, QJSValue *callback);
void import(QString func, QJSValue *callback);

signals:
Expand Down