Skip to content

Commit

Permalink
QML API 1.2: Handle JS exceptions in callbacks from Python
Browse files Browse the repository at this point in the history
Also improve error message in pyotherside.send() callback handler
to include filename and line number.
  • Loading branch information
thp committed Feb 12, 2014
1 parent b47ef00 commit 4028d4f
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 5 deletions.
18 changes: 17 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ io.thp.pyotherside 1.2
for names with dots. This means that ``importModule('x.y.z', ...)`` now
works like ``import x.y.z`` in Python.

* If a JavaScript exception occurs in the callback passed to
:func:`importModule` or :func:`call`, the signal :func:`error` is emitted
with the exception information (filename, line, message) as ``traceback``.

QML ``Python`` Element
----------------------

Expand Down Expand Up @@ -92,7 +96,7 @@ path and then importing the module asynchronously:
``file://`` from the path, so you can use :func:`Qt.resolvedUrl()`
without having to manually strip the leading ``file://`` in QML.

.. function:: importModule(string name, callable callback)
.. function:: importModule(string name, function callback(success) {})

Import a Python module.

Expand All @@ -102,6 +106,11 @@ path and then importing the module asynchronously:
(``import io.thp.pyotherside 1.2``), this behavior is now fixed,
and ``importModule('x.y.z, ...)`` behaves like ``import x.y.z``.

.. versionchanged:: 1.2.0
If a JavaScript exception occurs in the callback, the :func:`error`
signal is emitted with ``traceback`` containing the exception info
(QML API version 1.2 and newer).

Once modules are imported, Python function can be called on the
imported modules using:

Expand All @@ -112,6 +121,11 @@ imported modules using:
If ``callback`` is a callable, it will be called with the Python
function result as single argument when the call has succeeded.

.. versionchanged:: 1.2.0
If a JavaScript exception occurs in the callback, the :func:`error`
signal is emitted with ``traceback`` containing the exception info
(QML API version 1.2 and newer).

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 Down Expand Up @@ -698,6 +712,8 @@ Version 1.2.0dev (UNRELEASED)

* Introduced versioned QML imports for API change.
* QML API 1.2: Change :func:`importModule` behavior for imports with dots.
* QML API 1.2: Emit :func:`error` when JavaScript callbacks passed to
:func:`importModule` and :func:`call` throw an exception.

Version 1.1.0 (2014-02-06)
--------------------------
Expand Down
28 changes: 24 additions & 4 deletions src/qpython.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
#include <QJSEngine>


#define SINCE_API_VERSION(smaj, smin) \
((api_version_major > smaj) || (api_version_major == smaj && api_version_minor >= smin))


QPythonPriv *
QPython::priv = NULL;

Expand Down Expand Up @@ -165,8 +169,10 @@ QPython::receive(QVariant variant)
// call is asynchronous (it returns before we call into JS), so do
// the next best thing and report the error to our error handler in
// QML instead.
emit error(QString("pyotherside.send() failed handler: %1")
.arg(result.toString()));
emit error("pyotherside.send() failed handler: " +
result.property("fileName").toString() + ":" +
result.property("lineNumber").toString() + ": " +
result.toString());
}
} else {
// Default action
Expand Down Expand Up @@ -265,7 +271,14 @@ QPython::finished(QVariant result, QJSValue *callback)
QJSValueList args;
QJSValue v = callback->engine()->toScriptValue(result);
args << v;
callback->call(args);
QJSValue callbackResult = callback->call(args);
if (SINCE_API_VERSION(1, 2)) {
if (callbackResult.isError()) {
emit error(callbackResult.property("fileName").toString() + ":" +
callbackResult.property("lineNumber").toString() + ": " +
callbackResult.toString());
}
}
delete callback;
}

Expand All @@ -275,7 +288,14 @@ QPython::imported(bool result, QJSValue *callback)
QJSValueList args;
QJSValue v = callback->engine()->toScriptValue(QVariant(result));
args << v;
callback->call(args);
QJSValue callbackResult = callback->call(args);
if (SINCE_API_VERSION(1, 2)) {
if (callbackResult.isError()) {
emit error(callbackResult.property("fileName").toString() + ":" +
callbackResult.property("lineNumber").toString() + ": " +
callbackResult.toString());
}
}
delete callback;
}

Expand Down
41 changes: 41 additions & 0 deletions tests/test_callback_errors/test_callback_errors.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import QtQuick 2.0
import io.thp.pyotherside 1.2

// Test if PyOtherSide correctly reports JS errors happening in callbacks
// in signal error(string traceback) for both imports and function calls.

Python {
property var tests: ([])

function test_next() {
if (tests.length) {
tests.pop()();
} else {
console.log('Tests done');
Qt.quit();
}
}

Component.onCompleted: {
tests.unshift(function () {
console.log('Expecting ReferenceError for "invalid" on import');
importModule('os', function (success) {
invalid;
});
});
tests.unshift(function() {
console.log('Expecting TypeError for "lock" property');
call('os.getcwd', [], function (result) {
console.lock(result);
});
});
test_next();
}

onError: {
// Remove full path to .qml file
var msg = traceback.replace(Qt.resolvedUrl('.'), '');
console.log('Got error: ' + msg);
test_next();
}
}
5 changes: 5 additions & 0 deletions tests/test_callback_errors/test_callback_errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Expecting ReferenceError for "invalid" on import
Got error: test_callback_errors.qml:23: ReferenceError: invalid is not defined
Expecting TypeError for "lock" property
Got error: test_callback_errors.qml:29: TypeError: Property 'lock' of object [object Object] is not a function
Tests done

0 comments on commit 4028d4f

Please sign in to comment.