QVariant QPython::getattr(QVariant obj, QString attr) { if (!SINCE_API_VERSION(1, 4)) { emitError(QString("Import PyOtherSide 1.4 or newer to use getattr()")); return QVariant(); } ENSURE_GIL_STATE; PyObjectRef pyobj(convertQVariantToPyObject(obj), true); if (!pyobj) { emitError(QString("Failed to convert %1 to python object: '%1' (%2)").arg(obj.toString()).arg(priv->formatExc())); return QVariant(); } QByteArray byteArray = attr.toUtf8(); const char *attrStr = byteArray.data(); PyObjectRef o(PyObject_GetAttrString(pyobj.borrow(), attrStr), true); if (!o) { emitError(QString("Attribute not found: '%1' (%2)").arg(attr).arg(priv->formatExc())); return QVariant(); } return convertPyObjectToQVariant(o.borrow()); }
void TestPyOtherSide::testIntMoreThan32Bits() { // See https://github.com/thp/pyotherside/issues/86 // Affected: Devices and OSes where long is 32 bits, but long long is 64 bits long long two_fortytwo = 4398046511104LL; PyObject *o = PyLong_FromLongLong(two_fortytwo); QVERIFY(o); QVariant v = convertPyObjectToQVariant(o); QVERIFY(v.toLongLong() == two_fortytwo); }
QString QPythonPriv::formatExc() { PyObject *type = NULL; PyObject *value = NULL; PyObject *traceback = NULL; PyErr_Fetch(&type, &value, &traceback); PyErr_Clear(); if (type == NULL && value == NULL && traceback == NULL) { return "No Error"; } if (value != NULL && (type == NULL || traceback == NULL)) { return convertPyObjectToQVariant(PyObject_Str(value)).toString(); } PyObject *list = PyObject_CallMethod(traceback_mod, "format_exception", "OOO", type, value, traceback); Q_ASSERT(list != NULL); PyObject *n = PyUnicode_FromString("\n"); Q_ASSERT(n != NULL); PyObject *s = PyUnicode_Join(n, list); Q_ASSERT(s != NULL); if (s == NULL) { PyErr_Print(); return "Exception"; } QVariant v = convertPyObjectToQVariant(s); Q_ASSERT(v.isValid()); Py_DECREF(s); Py_DECREF(n); Py_DECREF(list); Py_DECREF(type); Py_DECREF(value); Py_DECREF(traceback); qDebug() << v.toString(); return v.toString(); }
QVariant QPython::evaluate(QString expr) { ENSURE_GIL_STATE; PyObjectRef o(priv->eval(expr), true); if (!o) { emitError(QString("Cannot evaluate '%1' (%2)").arg(expr).arg(priv->formatExc())); return QVariant(); } return convertPyObjectToQVariant(o.borrow()); }
void TestPyOtherSide::testConvertToPythonAndBack() { QVariantList l; l << "Hello" << 123 << 45.667 << true; QVariantList l2; l2 << "A" << QVariant() << "B" << 4711; l << QVariant(l2); QVariant v(l); PyObject *o = convertQVariantToPyObject(v); QVariant v2 = convertPyObjectToQVariant(o); QVERIFY(v == v2); }
void TestPyOtherSide::testPyObjectRefRoundTrip() { // Simulate a complete round-trip of a PyObject reference, from PyOtherSide // to QML and back. // Create a Python object, i.e. in a Python function. bool destructor_called = false; PyObject *o = PyCapsule_New(&destructor_called, "test", destruct); QVERIFY(o->ob_refcnt == 1); // Convert the object to a QVariant and increment its refcount. QVariant v = convertPyObjectToQVariant(o); // Decrement refcount and pass QVariant to QML. QVERIFY(o->ob_refcnt == 2); Py_DECREF(o); QVERIFY(o->ob_refcnt == 1); // Pass QVariant back to PyOtherSide, which converts it to a PyObject, // incrementing its refcount. PyObject *o2 = convertQVariantToPyObject(v); QVERIFY(o->ob_refcnt == 2); // The QVariant is deleted, i.e. by a JS variable falling out of scope. // This deletes the PyObjectRef and thus decrements the object's refcount. v = QVariant(); // At this point, we only have one reference (the one from o2) QVERIFY(o->ob_refcnt == 1); // There's still a reference, so the destructor must not have been called QVERIFY(!destructor_called); // Now, at this point, the last remaining reference is removed, which // will cause the destructor to be called Py_DECREF(o2); // There are no references left, so the capsule's destructor is called. QVERIFY(destructor_called); }
QVariant QPython::call_sync(QVariant func, QVariant args) { ENSURE_GIL_STATE; PyObjectRef callable; QString name; if (SINCE_API_VERSION(1, 4)) { if (static_cast<QMetaType::Type>(func.type()) == QMetaType::QString) { // Using version >= 1.4, but func is a string callable = PyObjectRef(priv->eval(func.toString()), true); name = func.toString(); } else { // Try to interpret "func" as a Python object callable = PyObjectRef(convertQVariantToPyObject(func), true); PyObjectRef repr = PyObjectRef(PyObject_Repr(callable.borrow()), true); name = convertPyObjectToQVariant(repr.borrow()).toString(); } } else { // Versions before 1.4 only support func as a string callable = PyObjectRef(priv->eval(func.toString()), true); name = func.toString(); } if (!callable) { emitError(QString("Function not found: '%1' (%2)").arg(name).arg(priv->formatExc())); return QVariant(); } QVariant v; QString errorMessage = priv->call(callable.borrow(), name, args, &v); if (!errorMessage.isNull()) { emitError(errorMessage); } return v; }
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)); }
QString QPythonPriv::formatExc() { PyObject *type = NULL; PyObject *value = NULL; PyObject *traceback = NULL; PyObject *list = NULL; PyObject *n = NULL; PyObject *s = NULL; PyErr_Fetch(&type, &value, &traceback); PyErr_NormalizeException(&type, &value, &traceback); QString message; QVariant v; if (type == NULL && value == NULL && traceback == NULL) { // No exception thrown? goto cleanup; } if (value != NULL) { // We can at least format the exception as string message = convertPyObjectToQVariant(PyObject_Str(value)).toString(); } if (type == NULL || traceback == NULL) { // Cannot get a traceback for this exception goto cleanup; } list = PyObject_CallMethod(traceback_mod, "format_exception", "OOO", type, value, traceback); if (list == NULL) { // Could not format exception, fall back to original message PyErr_Print(); goto cleanup; } n = PyUnicode_FromString("\n"); if (n == NULL) { PyErr_Print(); goto cleanup; } s = PyUnicode_Join(n, list); if (s == NULL) { PyErr_Print(); goto cleanup; } v = convertPyObjectToQVariant(s); if (v.isValid()) { message = v.toString(); } cleanup: Py_XDECREF(s); Py_XDECREF(n); Py_XDECREF(list); Py_XDECREF(type); Py_XDECREF(value); Py_XDECREF(traceback); qDebug() << QString("PyOtherSide error: %1").arg(message); return message; }
void QPythonPriv::receiveObject(PyObject *o) { emit receive(convertPyObjectToQVariant(o)); }