Skip to content

Commit

Permalink
Convert Python set and iterable to JS Array
Browse files Browse the repository at this point in the history
  • Loading branch information
thp committed Jun 10, 2014
1 parent 4548e7a commit 481400b
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 27 deletions.
12 changes: 9 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,10 @@ between Python and QML (and vice versa):
+--------------------+------------+-----------------------------+
| str | string | |
+--------------------+------------+-----------------------------+
| list | JS Array | |
| list | JS Array | JS Arrays are always |
| | | converted to Python lists. |
+--------------------+------------+-----------------------------+
| tuple | JS Array | JS Arrays are converted to |
| | | lists, not tuples |
| tuple | JS Array | |
+--------------------+------------+-----------------------------+
| dict | JS Object | Keys must be strings |
+--------------------+------------+-----------------------------+
Expand All @@ -330,6 +330,10 @@ between Python and QML (and vice versa):
+--------------------+------------+-----------------------------+
| datetime.datetime | JS Date | since PyOtherSide 1.2.0 |
+--------------------+------------+-----------------------------+
| set | JS Array | since PyOtherSide 1.3.0 |
+--------------------+------------+-----------------------------+
| iterable | JS Array | since PyOtherSide 1.3.0 |
+--------------------+------------+-----------------------------+

Trying to pass in other types than the ones listed here is undefined
behavior and will usually result in an error.
Expand Down Expand Up @@ -870,6 +874,8 @@ Version 1.3.0 (UNRELEASED)
* QML API 1.3: Import from Qt Resources (:func:`addImportPath` with ``qrc:/``).
* Add ``pyotherside.version`` constant to access version from Python as string.
* Support for building on Windows, build instructions for Windows builds.
* New data type conversions: Python ``set`` and iterable types (e.g. generator
expressions and generators) are converted to JS ``Array``.

Version 1.2.0 (2014-02-16)
--------------------------
Expand Down
1 change: 0 additions & 1 deletion src/converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ class ListIterator {
ListIterator() {}
virtual ~ListIterator() {}

virtual int count() = 0;
virtual bool next(V*) = 0;
};

Expand Down
45 changes: 28 additions & 17 deletions src/pyobject_converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,35 +62,46 @@ class PyObjectDictBuilder : public DictBuilder<PyObject *> {

class PyObjectListIterator : public ListIterator<PyObject *> {
public:
PyObjectListIterator(PyObject *&v) : list(v), pos(0) {}
virtual ~PyObjectListIterator() {}

virtual int count() {
if (PyList_Check(list)) {
return PyList_Size(list);
} else {
return PyTuple_Size(list);
PyObjectListIterator(PyObject *&v)
: list(v)
, iter(PyObject_GetIter(list))
, ref(NULL)
{
if (iter == NULL) {
// TODO: Handle error
}
}

virtual ~PyObjectListIterator()
{
Py_XDECREF(ref);
Py_XDECREF(iter);

if (PyErr_Occurred()) {
// TODO: Handle error
}
}

virtual bool next(PyObject **v) {
if (pos == count()) {
if (!iter) {
return false;
}

if (PyList_Check(list)) {
*v = PyList_GetItem(list, pos);
} else {
*v = PyTuple_GetItem(list, pos);
Py_XDECREF(ref);
ref = PyIter_Next(iter);

if (ref) {
*v = ref;
return true;
}

pos++;
return true;
return false;
}

private:
PyObject *list;
int pos;
PyObject *iter;
PyObject *ref;
};

class PyObjectDictIterator : public DictIterator<PyObject *> {
Expand Down Expand Up @@ -142,7 +153,7 @@ class PyObjectConverter : public Converter<PyObject *> {
return DATE;
} else if (PyTime_Check(o)) {
return TIME;
} else if (PyList_Check(o) || PyTuple_Check(o)) {
} else if (PyList_Check(o) || PyTuple_Check(o) || PySet_Check(o) || PyIter_Check(o)) {
return LIST;
} else if (PyDict_Check(o)) {
return DICT;
Expand Down
6 changes: 1 addition & 5 deletions src/qvariant_converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,8 @@ class QVariantListIterator : public ListIterator<QVariant> {
QVariantListIterator(QVariant &v) : list(v.toList()), pos(0) {}
virtual ~QVariantListIterator() {}

virtual int count() {
return list.size();
}

virtual bool next(QVariant *v) {
if (pos == count()) {
if (pos == list.size()) {
return false;
}

Expand Down
9 changes: 9 additions & 0 deletions tests/test_iterable/test_iterable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
def get_set():
return set((1, 2, 3))

def get_iterable_generator_expression():
return (x * 2 for x in range(4))

def get_iterable_generator():
for i in range(5):
yield i * 3
64 changes: 64 additions & 0 deletions tests/test_iterable/test_iterable.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import QtQuick 2.0
import io.thp.pyotherside 1.3

Python {
property var tests: ([])

Component.onCompleted: {
addImportPath(Qt.resolvedUrl('.'));

importModule('test_iterable', function () {
function test_next() {
console.log('================================');
if (tests.length == 0) {
console.log('Tests completed');
Qt.quit();
} else {
var test = tests.pop();
console.log('-> ' + test.name);
call(test.func, [], function (reply) {
if (reply === undefined || reply === null) {
error('Got undefined or null');
return;
}

// Sort, because a Python set is unordered (to make expected work below)
reply.sort(function (a, b) { return a - b; });

console.log('Got: ' + reply);
console.log('Expected: ' + test.expected);
if (reply.toString() !== test.expected.toString()) {
error('Results do not match');
return;
}
test_next();
});
}
}

tests.unshift({
name: 'Getting set returns JS array',
func: 'test_iterable.get_set',
expected: [1, 2, 3]
});
tests.unshift({
name: 'Getting generator expression returns JS array',
func: 'test_iterable.get_iterable_generator_expression',
expected: [0, 2, 4, 6]
});
tests.unshift({
name: 'Getting generator returns JS array',
func: 'test_iterable.get_iterable_generator',
expected: [0, 3, 6, 9, 12]
});

test_next();
});
}

onError: {
console.log('Error: ' + traceback);
console.log('Tests failed');
Qt.quit();
}
}
39 changes: 38 additions & 1 deletion tests/tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ test_converter_for(Converter<V> *conv)
v = builder->value();
delete builder;
ListIterator<V> *iterator = conv->list(v);
QVERIFY(iterator->count() == 2);
QVERIFY(iterator->next(&w));
QVERIFY(conv->type(w) == Converter<V>::INTEGER);
QVERIFY(conv->integer(w) == 444);
Expand Down Expand Up @@ -158,3 +157,41 @@ TestPyOtherSide::testEvaluate()
QPython13 py13;
testEvaluateWith(&py13);
}

void
TestPyOtherSide::testSetToList()
{
// Test if a Python set is converted to a list
PyObject *set = PySet_New(NULL);
QVERIFY(set != NULL);
PyObject *o = NULL;

o = PyLong_FromLong(123);
QVERIFY(o != NULL);
QVERIFY(PySet_Add(set, o) == 0);

o = PyLong_FromLong(321);
QVERIFY(o != NULL);
QVERIFY(PySet_Add(set, o) == 0);

o = PyLong_FromLong(444);
QVERIFY(o != NULL);
QVERIFY(PySet_Add(set, o) == 0);

// This will not be added (no duplicates in a set)
o = PyLong_FromLong(123);
QVERIFY(o != NULL);
QVERIFY(PySet_Add(set, o) == 0);

// At this point, we should have 3 items (123, 321 and 444)
QVERIFY(PySet_Size(set) == 3);

QVariant v = convertPyObjectToQVariant(set);
QVERIFY(v.canConvert(QMetaType::QVariantList));

QList<QVariant> l = v.toList();
QVERIFY(l.size() == 3);
QVERIFY(l.contains(123));
QVERIFY(l.contains(321));
QVERIFY(l.contains(444));
}
1 change: 1 addition & 0 deletions tests/tests.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class TestPyOtherSide : public QObject {
void testQVariantConverter();
void testPyObjectConverter();
void testConvertToPythonAndBack();
void testSetToList();
};

#endif /* PYOTHERSIDE_TESTS_H */

0 comments on commit 481400b

Please sign in to comment.