Skip to content

Commit

Permalink
Wrap generic PyObjects in an opaque value that can be passed to/from …
Browse files Browse the repository at this point in the history
…QML safely.
  • Loading branch information
fkrull committed Aug 16, 2014
1 parent e61c6fa commit 53a78e0
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 8 deletions.
9 changes: 9 additions & 0 deletions src/converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#ifndef PYOTHERSIDE_CONVERTER_H
#define PYOTHERSIDE_CONVERTER_H

typedef struct _object PyObject;

struct ConverterDate {
ConverterDate(int y, int m, int d)
: y(y), m(m), d(d)
Expand Down Expand Up @@ -102,6 +104,7 @@ class Converter {
DATE,
TIME,
DATETIME,
PYOBJECT,
};

virtual enum Type type(V&) = 0;
Expand All @@ -114,6 +117,8 @@ class Converter {
virtual ConverterDate date(V&) = 0;
virtual ConverterTime time(V&) = 0;
virtual ConverterDateTime dateTime(V&) = 0;
// `pyObject` must return a new reference to the object.
virtual PyObject *pyObject(V&) = 0;

virtual V fromInteger(long long v) = 0;
virtual V fromFloating(double v) = 0;
Expand All @@ -122,6 +127,8 @@ class Converter {
virtual V fromDate(ConverterDate date) = 0;
virtual V fromTime(ConverterTime time) = 0;
virtual V fromDateTime(ConverterDateTime dateTime) = 0;
// `fromPyObject` must keep the object's reference count constant.
virtual V fromPyObject(PyObject *pyobj) = 0;
virtual ListBuilder<V> *newList() = 0;
virtual DictBuilder<V> *newDict() = 0;
virtual V none() = 0;
Expand Down Expand Up @@ -186,6 +193,8 @@ convert(F from)
return tconv.fromTime(fconv.time(from));
case FC::DATETIME:
return tconv.fromDateTime(fconv.dateTime(from));
case FC::PYOBJECT:
return tconv.fromPyObject(fconv.pyObject(from));
}

return tconv.none();
Expand Down
13 changes: 7 additions & 6 deletions src/pyobject_converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,9 @@ class PyObjectConverter : public Converter<PyObject *> {
return DICT;
} else if (o == Py_None) {
return NONE;
} else {
return PYOBJECT;
}

fprintf(stderr, "Warning: Cannot convert:");
PyObject_Print(o, stderr, 0);
fprintf(stderr, "\n");

return NONE;
}

virtual long long integer(PyObject *&o) { return PyLong_AsLong(o); }
Expand Down Expand Up @@ -206,6 +202,10 @@ class PyObjectConverter : public Converter<PyObject *> {
PyDateTime_DATE_GET_SECOND(o),
PyDateTime_DATE_GET_MICROSECOND(o) / 1000);
}
virtual PyObject *pyObject(PyObject *&o) {
Py_XINCREF(o);
return o;
}

virtual PyObject * fromInteger(long long v) { return PyLong_FromLong((long)v); }
virtual PyObject * fromFloating(double v) { return PyFloat_FromDouble(v); }
Expand All @@ -216,6 +216,7 @@ class PyObjectConverter : public Converter<PyObject *> {
virtual PyObject * fromDateTime(ConverterDateTime v) {
return PyDateTime_FromDateAndTime(v.y, v.m, v.d, v.time.h, v.time.m, v.time.s, v.time.ms * 1000);
}
virtual PyObject * fromPyObject(PyObject *pyobj) { return pyobj; }
virtual ListBuilder<PyObject *> *newList() { return new PyObjectListBuilder(); }
virtual DictBuilder<PyObject *> *newDict() { return new PyObjectDictBuilder(); }
virtual PyObject * none() { Py_RETURN_NONE; }
Expand Down
57 changes: 57 additions & 0 deletions src/pyobject_ref.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

/**
* PyOtherSide: Asynchronous Python 3 Bindings for Qt 5
* Copyright (c) 2014, Felix Krull <f_krull@gmx.de>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
**/

#include "pyobject_ref.h"

class PyGIL {
public:
PyGIL(bool condition = true) : acquired(condition) {
if (acquired) { gil_state = PyGILState_Ensure(); }
}
~PyGIL() {
if (acquired) { PyGILState_Release(gil_state); }
}
private:
PyGILState_STATE gil_state;
bool acquired;
};

PyObjectRef::PyObjectRef(PyObject *obj) : pyobject(obj) {
PyGIL gil(pyobject != NULL);
Q_UNUSED(gil);
Py_XINCREF(pyobject);
}

PyObjectRef::PyObjectRef(const PyObjectRef &other) : pyobject(other.pyobject) {
PyGIL gil(pyobject != NULL);
Q_UNUSED(gil);
Py_XINCREF(pyobject);
}

PyObjectRef::~PyObjectRef() {
PyGIL gil(pyobject != NULL);
Q_UNUSED(gil);
Py_CLEAR(pyobject);
}

PyObject *PyObjectRef::newRef() const {
PyGIL gil(pyobject != NULL);
Q_UNUSED(gil);
Py_XINCREF(pyobject);
return pyobject;
}
38 changes: 38 additions & 0 deletions src/pyobject_ref.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

/**
* PyOtherSide: Asynchronous Python 3 Bindings for Qt 5
* Copyright (c) 2014, Felix Krull <f_krull@gmx.de>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
**/

#ifndef PYOTHERSIDE_PYOBJECT_REF_H
#define PYOTHERSIDE_PYOBJECT_REF_H

#include "Python.h"

#include <QMetaType>

class PyObjectRef {
public:
explicit PyObjectRef(PyObject *obj = NULL);
PyObjectRef(const PyObjectRef &other);
virtual ~PyObjectRef();
PyObject *newRef() const;
private:
PyObject *pyobject;
};

Q_DECLARE_METATYPE(PyObjectRef)

#endif // PYOTHERSIDE_PYOBJECT_REF_H
21 changes: 19 additions & 2 deletions src/qvariant_converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#define PYOTHERSIDE_QVARIANT_CONVERTER_H

#include "converter.h"
#include "pyobject_ref.h"

#include <QVariant>
#include <QTime>
Expand Down Expand Up @@ -139,8 +140,13 @@ class QVariantConverter : public Converter<QVariant> {
case QMetaType::UnknownType:
return NONE;
default:
qDebug() << "Cannot convert:" << v;
return NONE;
int userType = v.userType();
if (userType == qMetaTypeId<PyObjectRef>()) {
return PYOBJECT;
} else {
qDebug() << "Cannot convert:" << v;
return NONE;
}
}
}

Expand Down Expand Up @@ -187,6 +193,10 @@ class QVariantConverter : public Converter<QVariant> {
return stringstorage.constData();
}

virtual PyObject *pyObject(QVariant &v) {
return v.value<PyObjectRef>().newRef();
}

virtual ListBuilder<QVariant> *newList() {
return new QVariantListBuilder;
}
Expand All @@ -206,6 +216,13 @@ class QVariantConverter : public Converter<QVariant> {
QTime t(v.time.h, v.time.m, v.time.s, v.time.ms);
return QVariant(QDateTime(d, t));
}
virtual QVariant fromPyObject(PyObject *pyobj) {
QVariant v = QVariant::fromValue(PyObjectRef(pyobj));
// We consume the reference that was passed, mirroring
// PyObjectConverter::pyObject.
Py_CLEAR(pyobj);
return v;
}
virtual QVariant none() { return QVariant(); };

private:
Expand Down
4 changes: 4 additions & 0 deletions src/src.pro
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ HEADERS += qpython_priv.h
SOURCES += global_libpython_loader.cpp
HEADERS += global_libpython_loader.h

# Reference-counting PyObject wrapper class
SOURCES += pyobject_ref.cpp
HEADERS += pyobject_ref.h

# Type System Conversion Logic
HEADERS += converter.h
HEADERS += qvariant_converter.h
Expand Down
7 changes: 7 additions & 0 deletions tests/tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ test_converter_for(Converter<V> *conv)
QVERIFY(conv->boolean(x) == true);
delete iterator2;

/* Convert from/to generic PyObject */
PyObject *obj = PyCapsule_New(conv, "test", NULL);
v = conv->fromPyObject(obj);
QVERIFY(conv->type(v) == Converter<V>::PYOBJECT);
QVERIFY(conv->pyObject(v) == obj);
Py_CLEAR(obj);

delete conv;
}

Expand Down
1 change: 1 addition & 0 deletions tests/tests.pro
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ HEADERS += tests.h
SOURCES += ../src/qpython.cpp
SOURCES += ../src/qpython_worker.cpp
SOURCES += ../src/qpython_priv.cpp
SOURCES += ../src/pyobject_ref.cpp

HEADERS += ../src/qpython.h
HEADERS += ../src/qpython_worker.h
Expand Down

0 comments on commit 53a78e0

Please sign in to comment.