aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/libpysideremoteobjects/pysiderephandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside6/libpysideremoteobjects/pysiderephandler.cpp')
-rw-r--r--sources/pyside6/libpysideremoteobjects/pysiderephandler.cpp459
1 files changed, 459 insertions, 0 deletions
diff --git a/sources/pyside6/libpysideremoteobjects/pysiderephandler.cpp b/sources/pyside6/libpysideremoteobjects/pysiderephandler.cpp
new file mode 100644
index 000000000..25bdbef9b
--- /dev/null
+++ b/sources/pyside6/libpysideremoteobjects/pysiderephandler.cpp
@@ -0,0 +1,459 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "pysiderephandler_p.h"
+#include "pysidedynamicclass_p.h"
+#include "pysidedynamicpod_p.h"
+#include "pysidedynamiccommon_p.h"
+
+#include <pep384ext.h>
+#include <sbkstring.h>
+#include <sbktypefactory.h>
+#include <signature.h>
+
+#include <pysideutils.h>
+
+#include <QtCore/qbuffer.h>
+#include <QtCore/qiodevice.h>
+#include <QtCore/qmetaobject.h>
+
+#include <QtRemoteObjects/qremoteobjectreplica.h>
+#include <QtRemoteObjects/qremoteobjectpendingcall.h>
+
+#include <private/qremoteobjectrepparser_p.h>
+
+using namespace Qt::StringLiterals;
+using namespace Shiboken;
+
+/**
+ * @file pysiderephandler.cpp
+ * @brief This file contains the implementation of the PySideRepFile type and its
+ * associated methods for handling Qt Remote Objects in PySide6.
+ *
+ * The PySideRepFile type provides functionality to parse and handle Qt Remote Objects
+ * (QtRO) files, and dynamically generate Python types for QtRO sources, replicas, and
+ * PODs (Plain Old Data structures).
+ *
+ * The RepFile_tp_methods array defines the methods available on the PySideRepFile object:
+ * - source: Generates a dynamic Python type for a QtRO source class.
+ * - replica: Generates a dynamic Python type for a QtRO replica class.
+ * - pod: Generates a dynamic Python type for a QtRO POD class.
+ *
+ * When generating a source or replica type, the generateDynamicType function is
+ * used, creating a new Python type based on the generated QMetaObject, and adds
+ * method descriptors for the required methods. A QVariantList for the types
+ * properties is also created, populated with default values if set in the input
+ * .rep file.
+*/
+
+static QVariantList generateProperties(QMetaObject *meta, const ASTClass &astClass);
+
+extern "C"
+{
+
+// Code for the PySideRepFile type
+static PyObject *RepFile_tp_string(PyObject *self);
+static PyObject *RepFile_tp_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds);
+static int RepFile_tp_init(PyObject *self, PyObject *args, PyObject *kwds);
+static void RepFile_tp_free(void *self);
+static void RepFile_tp_dealloc(PySideRepFile *self);
+
+static PyObject *RepFile_get_pods(PySideRepFile *self, void * /*unused*/);
+static PyObject *RepFile_get_replicas(PySideRepFile *self, void * /*unused*/);
+static PyObject *RepFile_get_sources(PySideRepFile *self, void * /*unused*/);
+
+bool instantiateFromDefaultValue(QVariant &variant, const QString &defaultValue);
+
+static PyObject *cppToPython_POD_Tuple(const void *cppIn);
+static void pythonToCpp_Tuple_POD(PyObject *pyIn, void *cppOut);
+static PythonToCppFunc is_Tuple_PythonToCpp_POD_Convertible(PyObject *pyIn);
+
+static PyGetSetDef RepFile_tp_getters[] = {
+ {"pod", reinterpret_cast<getter>(RepFile_get_pods), nullptr, "POD dictionary", nullptr},
+ {"replica", reinterpret_cast<getter>(RepFile_get_replicas), nullptr, "Replica dictionary", nullptr},
+ {"source", reinterpret_cast<getter>(RepFile_get_sources), nullptr, "Source dictionary", nullptr},
+ {nullptr, nullptr, nullptr, nullptr, nullptr} // Sentinel
+};
+
+static PyTypeObject *createRepFileType()
+{
+ PyType_Slot PySideRepFileType_slots[] = {
+ {Py_tp_str, reinterpret_cast<void *>(RepFile_tp_string)},
+ {Py_tp_init, reinterpret_cast<void *>(RepFile_tp_init)},
+ {Py_tp_new, reinterpret_cast<void *>(RepFile_tp_new)},
+ {Py_tp_free, reinterpret_cast<void *>(RepFile_tp_free)},
+ {Py_tp_dealloc, reinterpret_cast<void *>(RepFile_tp_dealloc)},
+ {Py_tp_getset, reinterpret_cast<void *>(RepFile_tp_getters)},
+ {0, nullptr}
+ };
+
+ PyType_Spec PySideRepFileType_spec = {
+ "2:PySide6.QtRemoteObjects.RepFile",
+ sizeof(PySideRepFile),
+ 0,
+ Py_TPFLAGS_DEFAULT,
+ PySideRepFileType_slots};
+ return SbkType_FromSpec(&PySideRepFileType_spec);
+}
+
+PyTypeObject *PySideRepFile_TypeF(void)
+{
+ static auto *type = createRepFileType();
+ return type;
+}
+
+static PyObject *RepFile_tp_string(PyObject *self)
+{
+ auto *cppSelf = reinterpret_cast<PySideRepFile *>(self);
+ QString result = QStringLiteral("RepFile(Classes: [%1], PODs: [%2])")
+ .arg(cppSelf->d->classes.join(", "_L1), cppSelf->d->pods.join(", "_L1));
+ return PyUnicode_FromString(result.toUtf8().constData());
+}
+
+static PyObject *RepFile_tp_new(PyTypeObject *subtype, PyObject * /* args */, PyObject * /* kwds */)
+{
+ auto *me = PepExt_TypeCallAlloc<PySideRepFile>(subtype, 0);
+ auto *priv = new PySideRepFilePrivate;
+ priv->podDict = PyDict_New();
+ if (!priv->podDict) {
+ delete priv;
+ return nullptr;
+ }
+ priv->replicaDict = PyDict_New();
+ if (!priv->replicaDict) {
+ Py_DECREF(priv->podDict);
+ delete priv;
+ return nullptr;
+ }
+ priv->sourceDict = PyDict_New();
+ if (!priv->sourceDict) {
+ Py_DECREF(priv->podDict);
+ Py_DECREF(priv->replicaDict);
+ delete priv;
+ return nullptr;
+ }
+ me->d = priv;
+ return reinterpret_cast<PyObject *>(me);
+}
+
+static PyObject *RepFile_get_pods(PySideRepFile *self, void * /* closure */)
+{
+ Py_INCREF(self->d->podDict);
+ return self->d->podDict;
+}
+
+static PyObject *RepFile_get_replicas(PySideRepFile *self, void * /* closure */)
+{
+ Py_INCREF(self->d->replicaDict);
+ return self->d->replicaDict;
+}
+
+static PyObject *RepFile_get_sources(PySideRepFile *self, void * /* closure */)
+{
+ Py_INCREF(self->d->sourceDict);
+ return self->d->sourceDict;
+}
+
+static void RepFile_tp_dealloc(PySideRepFile *self)
+{
+ Py_XDECREF(self->d->podDict);
+ Py_XDECREF(self->d->replicaDict);
+ Py_XDECREF(self->d->sourceDict);
+ PepExt_TypeCallFree(reinterpret_cast<PyObject *>(self));
+}
+
+static int parseArgsToAST(PyObject *args, PySideRepFile *repFile)
+{
+ // Verify args is a single string argument
+ if (PyTuple_Size(args) != 1 || !PyUnicode_Check(PyTuple_GetItem(args, 0))) {
+ PyErr_SetString(PyExc_TypeError, "RepFile constructor requires a single string argument");
+ return -1;
+ }
+
+ // Wrap contents into a QBuffer
+ const auto contents = PySide::pyStringToQString(PyTuple_GetItem(args, 0));
+ auto byteArray = contents.toUtf8();
+ QBuffer buffer(&byteArray);
+ buffer.open(QIODevice::ReadOnly);
+ RepParser repparser(buffer);
+ if (!repparser.parse()) {
+ PyErr_Format(PyExc_RuntimeError, "Error parsing input, line %d: error: %s",
+ repparser.lineNumber(), qPrintable(repparser.errorString()));
+ auto lines = contents.split("\n"_L1);
+ auto lMin = std::max(1, repparser.lineNumber() - 2);
+ auto lMax = std::min(repparser.lineNumber() + 2, int(lines.size()));
+ // Print a few lines around the error
+ qWarning() << "Contents:";
+ for (int i = lMin; i <= lMax; ++i) {
+ if (i == repparser.lineNumber())
+ qWarning().nospace() << " line " << i << ": > " << lines.at(i - 1);
+ else
+ qWarning().nospace() << " line " << i << ": " << lines.at(i - 1);
+ }
+ return -1;
+ }
+
+ repFile->d->ast = repparser.ast();
+
+ return 0;
+}
+
+static const char *repName(QMetaObject *meta)
+{
+ const int ind = meta->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE);
+ return ind >= 0 ? meta->classInfo(ind).value() : "<Invalid RemoteObject>";
+}
+
+static int RepFile_tp_init(PyObject *self, PyObject *args, PyObject * /* kwds */)
+{
+ auto *cppSelf = reinterpret_cast<PySideRepFile *>(self);
+ if (parseArgsToAST(args, cppSelf) < 0)
+ return -1;
+
+ for (const auto &pod : std::as_const(cppSelf->d->ast.pods)) {
+ cppSelf->d->pods << pod.name;
+ auto *qobject = new QObject;
+ auto *meta = createAndRegisterMetaTypeFromPOD(pod, qobject);
+ if (!meta) {
+ delete qobject;
+ PyErr_Format(PyExc_RuntimeError, "Failed to create meta object for POD '%s'",
+ pod.name.toUtf8().constData());
+ return -1;
+ }
+
+ PyTypeObject *newType = createPodType(meta);
+ if (!newType) {
+ delete qobject;
+ PyErr_Print();
+ PyErr_Format(PyExc_RuntimeError, "Failed to create POD type for POD '%s'",
+ pod.name.toUtf8().constData());
+ return -1;
+ }
+ if (set_cleanup_capsule_attr_for_pointer(newType, "_qobject_capsule", qobject) < 0) {
+ delete qobject;
+ return -1;
+ }
+
+ PyDict_SetItemString(cppSelf->d->podDict, meta->className(),
+ reinterpret_cast<PyObject *>(newType));
+ Py_DECREF(newType);
+ }
+
+ if (PyErr_Occurred())
+ PyErr_Print();
+
+ for (const auto &cls : std::as_const(cppSelf->d->ast.classes)) {
+ cppSelf->d->classes << cls.name;
+
+ // Create Source type
+ {
+ auto *qobject = new QObject;
+ auto *meta = createAndRegisterSourceFromASTClass(cls, qobject);
+ if (!meta) {
+ delete qobject;
+ PyErr_Format(PyExc_RuntimeError, "Failed to create Source meta object for class '%s'",
+ cls.name.toUtf8().constData());
+ return -1;
+ }
+
+ auto properties = generateProperties(meta, cls);
+ // Check if an error occurred during generateProperties
+ if (PyErr_Occurred()) {
+ delete qobject;
+ return -1;
+ }
+ auto *propertiesPtr = new QVariantList(properties);
+ auto *pyCapsule = PyCapsule_New(propertiesPtr, nullptr, [](PyObject *capsule) {
+ delete reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(capsule, nullptr));
+ });
+
+ PyTypeObject *newType = createDynamicClass(meta, pyCapsule);
+ if (!newType) {
+ delete qobject;
+ PyErr_Format(PyExc_RuntimeError,
+ "Failed to create Source Python type for class '%s'",
+ meta->className());
+ return -1;
+ }
+ if (set_cleanup_capsule_attr_for_pointer(newType, "_qobject_capsule", qobject) < 0) {
+ delete qobject;
+ return -1;
+ }
+
+ PyDict_SetItemString(cppSelf->d->sourceDict, repName(meta),
+ reinterpret_cast<PyObject *>(newType));
+ Py_DECREF(newType);
+ }
+
+ // Create Replica type
+ {
+ auto *qobject = new QObject;
+ auto *meta = createAndRegisterReplicaFromASTClass(cls, qobject);
+ if (!meta) {
+ delete qobject;
+ PyErr_Format(PyExc_RuntimeError,
+ "Failed to create Replica meta object for class '%s'",
+ qPrintable(cls.name));
+ return -1;
+ }
+
+ auto properties = generateProperties(meta, cls);
+ // Check if an error occurred during generateProperties
+ if (PyErr_Occurred()) {
+ delete qobject;
+ return -1;
+ }
+ auto *propertiesPtr = new QVariantList(properties);
+ auto *pyCapsule = PyCapsule_New(propertiesPtr, nullptr, [](PyObject *capsule) {
+ delete reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(capsule, nullptr));
+ });
+
+ PyTypeObject *newType = createDynamicClass(meta, pyCapsule);
+ if (!newType) {
+ delete qobject;
+ PyErr_Format(PyExc_RuntimeError,
+ "Failed to create Replica Python type for class '%s'",
+ meta->className());
+ return -1;
+ }
+ if (set_cleanup_capsule_attr_for_pointer(newType, "_qobject_capsule", qobject) < 0) {
+ delete qobject;
+ return -1;
+ }
+
+ PyDict_SetItemString(cppSelf->d->replicaDict, repName(meta),
+ reinterpret_cast<PyObject *>(newType));
+ Py_DECREF(newType);
+ }
+ }
+
+ return 0;
+}
+
+static void RepFile_tp_free(void *self)
+{
+ PySideRepFile *obj = reinterpret_cast<PySideRepFile*>(self);
+ delete obj->d;
+}
+
+/**
+ * @brief Sets the QVariant value based on the provided default value text.
+ *
+ * This function attempts to set the provided QVariant's value based on the
+ * provided text. It evaluates the text as a Python expression, the the python
+ * type associated with the provided QMetaType. It first retrieves the Python
+ * type object corresponding to the given QMetaType, then constructs a Python
+ * expression to instantiate the type with the default value. The expression is
+ * evaluated using PyRun_String, and the result is then set on the QVariant.
+ * Note: The variant is passed by reference and modified in place.
+ *
+ * @return True if the instantiation is successful, false otherwise.
+ */
+bool instantiateFromDefaultValue(QVariant &variant, const QString &defaultValue)
+{
+ auto metaType = variant.metaType();
+ auto *pyType = Shiboken::Conversions::getPythonTypeObject(metaType.name());
+ if (!pyType) {
+ PyErr_Format(PyExc_TypeError, "Failed to find Python type for meta type: %s",
+ metaType.name());
+ return false;
+ }
+
+ // Evaluate the code
+ static PyObject *pyLocals = PyDict_New();
+
+ // Create the Python expression to evaluate
+ std::string code = std::string(pyType->tp_name) + '('
+ + defaultValue.toUtf8().constData() + ')';
+ PyObject *pyResult = PyRun_String(code.c_str(), Py_eval_input, pyLocals, pyLocals);
+
+ if (!pyResult) {
+ PyObject *ptype = nullptr;
+ PyObject *pvalue = nullptr;
+ PyObject *ptraceback = nullptr;
+ PyErr_Fetch(&ptype, &pvalue, &ptraceback);
+ PyErr_NormalizeException(&ptype, &pvalue, &ptraceback);
+ PyErr_Format(PyExc_TypeError,
+ "Failed to generate default value. Error: %s. Problematic code: %s",
+ Shiboken::String::toCString(PyObject_Str(pvalue)), code.c_str());
+ Py_XDECREF(ptype);
+ Py_XDECREF(pvalue);
+ Py_XDECREF(ptraceback);
+ Py_DECREF(pyLocals);
+ return false;
+ }
+
+ Conversions::SpecificConverter converter(metaType.name());
+ if (!converter) {
+ PyErr_Format(PyExc_TypeError, "Failed to find converter from Python type: %s to Qt type: %s",
+ pyResult->ob_type->tp_name, metaType.name());
+ Py_DECREF(pyResult);
+ return false;
+ }
+ converter.toCpp(pyResult, variant.data());
+ Py_DECREF(pyResult);
+
+ return true;
+}
+
+} // extern "C"
+
+static QVariantList generateProperties(QMetaObject *meta, const ASTClass &astClass)
+{
+ QVariantList properties;
+ auto propertyCount = astClass.properties.size();
+ properties.reserve(propertyCount);
+ for (auto i = 0; i < propertyCount; ++i) {
+ auto j = i + meta->propertyOffset(); // Corresponding property index in the meta object
+ auto metaProperty = meta->property(j);
+ auto metaType = metaProperty.metaType();
+ if (!metaType.isValid()) {
+ PyErr_Format(PyExc_RuntimeError, "Invalid meta type for property %d: %s", i,
+ astClass.properties[i].type.toUtf8().constData());
+ return {};
+ }
+ auto variant = QVariant(metaType);
+ if (auto defaultValue = astClass.properties[i].defaultValue; !defaultValue.isEmpty()) {
+ auto success = instantiateFromDefaultValue(variant, defaultValue);
+ if (!success) {
+ // Print a warning giving the property name, then propagate the error
+ qWarning() << "Failed to instantiate default value for property: "
+ << metaProperty.name();
+ return {};
+ }
+ }
+ properties << variant;
+ }
+ return properties;
+}
+
+namespace PySide::RemoteObjects
+{
+
+static const char *RepFile_SignatureStrings[] = {
+ "PySide6.RemoteObjects.RepFile(self,content:str)",
+ nullptr}; // Sentinel
+
+void init(PyObject *module)
+{
+ if (InitSignatureStrings(PySideRepFile_TypeF(), RepFile_SignatureStrings) < 0)
+ return;
+
+ qRegisterMetaType<QRemoteObjectPendingCall>();
+ qRegisterMetaType<QRemoteObjectPendingCallWatcher>();
+
+ Py_INCREF(PySideRepFile_TypeF());
+ PyModule_AddObject(module, "RepFile", reinterpret_cast<PyObject *>(PySideRepFile_TypeF()));
+
+ // Add a test helper to verify type reference counting
+ static PyMethodDef get_capsule_count_def = {
+ "getCapsuleCount", // name of the function in Python
+ reinterpret_cast<PyCFunction>(get_capsule_count), // C function pointer
+ METH_NOARGS, // flags indicating parameters
+ "Returns the current count of PyCapsule objects" // docstring
+ };
+
+ PyModule_AddObject(module, "getCapsuleCount", PyCFunction_New(&get_capsule_count_def, nullptr));
+}
+
+} // namespace PySide::RemoteObjects