/* * * Copyright (C) 2010 MeVis Medical Solutions AG All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * Further, this software is distributed without any warranty that it is * free of the rightful claim of any third person regarding infringement * or the like. Any license provided herein, whether implied or * otherwise, applies only to this software file. Patent licenses, if * any, provided herein do not apply to combinations of this program with * other software, or any other product whatsoever. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Contact information: MeVis Medical Solutions AG, Universitaetsallee 29, * 28359 Bremen, Germany or: * * http://www.mevis.de * */ //---------------------------------------------------------------------------------- /*! // \file PythonQt.cpp // \author Florian Link // \author Last changed by $Author: florian $ // \date 2006-05 */ //---------------------------------------------------------------------------------- #include "PythonQt.h" #include "PythonQtImporter.h" #include "PythonQtClassInfo.h" #include "PythonQtMethodInfo.h" #include "PythonQtSignal.h" #include "PythonQtSignalReceiver.h" #include "PythonQtConversion.h" #include "PythonQtProperty.h" #include "PythonQtStdIn.h" #include "PythonQtStdOut.h" #include "PythonQtCppWrapperFactory.h" #include "PythonQtVariants.h" #include "PythonQtStdDecorators.h" #include "PythonQtQFileImporter.h" #include "PythonQtBoolResult.h" #include "PythonQtSlotDecorator.h" #include #if PY_VERSION_HEX >= 0x030A0000 #include #else #include #endif // .co_varnames was removed in 3.11, but the helper function was added #if PY_VERSION_HEX < 0x030B0000 static inline PyObject* PyCode_GetVarnames(PyCodeObject* o) { Py_XINCREF(o->co_varnames); return o->co_varnames; } #endif #include PythonQt* PythonQt::_self = nullptr; int PythonQt::_uniqueModuleCount = 0; void PythonQt_init_QtGuiBuiltin(PyObject*); void PythonQt_init_QtCoreBuiltin(PyObject*); void PythonQt::init(int flags, const QByteArray& pythonQtModuleName) { if (!_self) { _self = new PythonQt(flags, pythonQtModuleName); _self->_p->_PythonQtObjectPtr_metaId = qRegisterMetaType("PythonQtObjectPtr"); PythonQtConv::registerMetaTypeToPythonConverter(_self->_p->_PythonQtObjectPtr_metaId, PythonQtConv::convertFromPythonQtObjectPtr); PythonQtConv::registerPythonToMetaTypeConverter(_self->_p->_PythonQtObjectPtr_metaId, PythonQtConv::convertToPythonQtObjectPtr); _self->_p->_PythonQtSafeObjectPtr_metaId = qRegisterMetaType("PythonQtSafeObjectPtr"); PythonQtConv::registerMetaTypeToPythonConverter(_self->_p->_PythonQtSafeObjectPtr_metaId, PythonQtConv::convertFromPythonQtSafeObjectPtr); PythonQtConv::registerPythonToMetaTypeConverter(_self->_p->_PythonQtSafeObjectPtr_metaId, PythonQtConv::convertToPythonQtSafeObjectPtr); PythonQtObjectPtr importlib; importlib.setNewRef(PyImport_ImportModule("importlib.machinery")); if (importlib) { _self->_p->_pySourceFileLoader = importlib.getVariable("SourceFileLoader"); _self->_p->_pySourcelessFileLoader = importlib.getVariable("SourcelessFileLoader"); } // Import asyncio only when not explicitly disabled. // Importing asyncio on Py3.12+ pulls in ssl/_ssl; some environments/tests // want to avoid that during early embedded init. if (!qEnvironmentVariableIsSet("PYTHONQT_DISABLE_ASYNCIO")) { PythonQtObjectPtr asyncio; asyncio.setNewRef(PyImport_ImportModule("asyncio")); if (asyncio) { _self->_p->_pyEnsureFuture = asyncio.getVariable("ensure_future"); _self->_p->_pyFutureClass = asyncio.getVariable("Future"); } } PythonQt::priv()->setupSharedLibrarySuffixes(); PythonQtMethodInfo::addParameterTypeAlias("QObjectList", "QList"); qRegisterMetaType>("QList"); // QObjectList might already be registered by other modules (e.g. QtScript) or under different type names. // To avoid conflicts or warnings, only register it if it is currently unknown. if (QMetaType::type("QObjectList") == QMetaType::UnknownType) { qRegisterMetaType("QObjectList"); } qRegisterMetaType>("QList"); if (QT_POINTER_SIZE == 8) { qRegisterMetaType("size_t"); } else { qRegisterMetaType("size_t"); } PythonQtConv::registerStringViewTypes(); int objectPtrListId = qRegisterMetaType>("QList"); PythonQtConv::registerMetaTypeToPythonConverter(objectPtrListId, PythonQtConv::convertFromQListOfPythonQtObjectPtr); PythonQtConv::registerPythonToMetaTypeConverter(objectPtrListId, PythonQtConv::convertToQListOfPythonQtObjectPtr); PythonQtRegisterToolClassesTemplateConverter(int); PythonQtRegisterToolClassesTemplateConverter(bool); PythonQtRegisterToolClassesTemplateConverter(float); PythonQtRegisterToolClassesTemplateConverter(double); PythonQtRegisterToolClassesTemplateConverter(qint32); PythonQtRegisterToolClassesTemplateConverter(quint32); PythonQtRegisterToolClassesTemplateConverter(qint64); PythonQtRegisterToolClassesTemplateConverter(quint64); #ifdef PYTHONQT_SUPPORT_ML_TYPES PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); #endif PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); // Qt 4 uses uint, while Qt 5 uses unsigned int, seems to be a moc change... PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QVector", "QVector"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); PythonQtMethodInfo::addParameterTypeAlias("QList", "QList"); // register some QPairs that are used in the Qt interfaces: PythonQtRegisterQPairConverter(int, int); PythonQtRegisterQPairConverter(float, float); PythonQtRegisterQPairConverter(double, double); PythonQtRegisterQPairConverter(QString, QString); PythonQtRegisterQPairConverter(QByteArray, QByteArray); PythonQtRegisterQPairConverter(double, QColor); PythonQtRegisterQPairConverter(double, QPointF); PythonQtRegisterQPairConverter(double, QVariant); PythonQtRegisterQPairConverter(QString, QSizeF); PythonQtMethodInfo::addParameterTypeAlias("QPair", "QPair"); PythonQtMethodInfo::addParameterTypeAlias("QPair", "QPair"); PythonQtMethodInfo::addParameterTypeAlias("QPair", "QPair"); PythonQtMethodInfo::addParameterTypeAlias("QPair", "QPair"); PythonQtMethodInfo::addParameterTypeAlias("QPair", "QPair"); // register some QList/QVector of QPairs that are used in the Qt interfaces: PythonQtRegisterListTemplateQPairConverter(QVector, double, QVariant); PythonQtRegisterListTemplateQPairConverter(QVector, double, QColor); // NOTE: the extra space between the > is needed (and added by the moc) PythonQtMethodInfo::addParameterTypeAlias("QVector >", "QVector >"); PythonQtMethodInfo::addParameterTypeAlias("QVector >", "QVector >"); PythonQtRegisterListTemplateQPairConverter(QList, QByteArray, QByteArray); PythonQtRegisterListTemplateQPairConverter(QList, QString, QString); PythonQtRegisterListTemplateQPairConverter(QList, QString, QSizeF); PythonQtRegisterListTemplateQPairConverter(QList, double, QPointF); PythonQtRegisterListTemplateQPairConverter(QList, double, double); // NOTE: the extra space between the > is needed (and added by the moc) PythonQtMethodInfo::addParameterTypeAlias("QList >", "QList >"); PythonQtMethodInfo::addParameterTypeAlias("QList >", "QList >"); PythonQtRegisterIntegerMapConverter(QMap, QByteArray); PythonQtRegisterIntegerMapConverter(QMap, QVariant); PythonQtRegisterIntegerMapConverter(QMap, QString); PythonQtRegisterIntegerMapConverter(QHash, QByteArray); PythonQtRegisterIntegerMapConverter(QHash, QVariant); PythonQtRegisterIntegerMapConverter(QHash, QString); PythonQtMethodInfo::addParameterTypeAlias("QHash", "QHash"); PythonQt_init_QtCoreBuiltin(nullptr); PythonQt_init_QtGuiBuiltin(nullptr); PythonQt::self()->addDecorators(new PythonQtStdDecorators()); PythonQt::self()->registerCPPClass("QMetaObject", nullptr, "QtCore", PythonQtCreateObject); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QByteArray); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QDate); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QTime); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QDateTime); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QUrl); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QLocale); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QRect); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QRectF); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QSize); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QSizeF); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QLine); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QLineF); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QPoint); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QPointF); #if QT_VERSION < 0x060000 PythonQtRegisterToolClassesTemplateConverterForKnownClass(QRegExp); #endif PythonQtRegisterToolClassesTemplateConverterForKnownClass(QRegularExpression); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QFont); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QPixmap); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QBrush); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QColor); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QPalette); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QIcon); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QImage); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QPolygon); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QRegion); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QBitmap); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QCursor); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QSizePolicy); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QKeySequence); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QPen); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QTextLength); PythonQtRegisterToolClassesTemplateConverterForKnownClass(QTextFormat); #if QT_VERSION < 0x060000 PythonQtRegisterToolClassesTemplateConverterForKnownClass(QMatrix); #endif PyObject* pack = PythonQt::priv()->packageByName("QtCore"); PyObject* pack2 = PythonQt::priv()->packageByName("Qt"); PyObject* qtNamespace = PythonQt::priv()->getClassInfo("Qt")->pythonQtClassWrapper(); const char* names[16] = {"SIGNAL", "SLOT", "qAbs", "qBound", "qDebug", "qWarning", "qCritical", "qFatal", "qFuzzyCompare", "qMax", "qMin", "qRound", "qRound64", "qVersion", "qrand", "qsrand"}; for (unsigned int i = 0; i < sizeof(names) / sizeof(names[0]); i++) { PyObject* obj = PyObject_GetAttrString(qtNamespace, names[i]); if (obj) { if (PyModule_AddObject(pack, names[i], obj) < 0) { std::cerr << "failed to add " << names[i] << " to QtCore\n"; } else { Py_INCREF(obj); } if (PyModule_AddObject(pack2, names[i], obj) < 0) { Py_DECREF(obj); std::cerr << "failed to add " << names[i] << " to Qt\n"; } } else { std::cerr << "method not found " << names[i] << std::endl; } } int enumValues[] = {QtDebugMsg, QtWarningMsg, QtCriticalMsg, QtFatalMsg, QtSystemMsg}; const char* enumNames[] = {"QtDebugMsg", "QtWarningMsg", "QtCriticalMsg", "QtFatalMsg", "QtSystemMsg"}; for (auto i = 0u; i < sizeof(enumValues) / sizeof(int); i++) { PyObject* obj = PyLong_FromLong(enumValues[i]); if (PyModule_AddObject(pack, enumNames[i], obj) == 0) { Py_INCREF(obj); } if (PyModule_AddObject(pack2, enumNames[i], obj) < 0) { Py_DECREF(obj); } } _self->priv()->pythonQtModule().addObject("Debug", _self->priv()->_debugAPI); _self->priv()->pythonQtModule().addObject("Config", _self->priv()->_configAPI); Py_INCREF((PyObject*)&PythonQtSlotDecorator_Type); Py_INCREF((PyObject*)&PythonQtSignalFunction_Type); Py_INCREF((PyObject*)&PythonQtProperty_Type); PyModule_AddObject(pack, "Slot", (PyObject*)&PythonQtSlotDecorator_Type); PyModule_AddObject(pack, "Signal", (PyObject*)&PythonQtSignalFunction_Type); PyModule_AddObject(pack, "Property", (PyObject*)&PythonQtProperty_Type); } } void PythonQt::cleanup() { if (_self) { // Remove signal handlers in advance, since destroying them calls back into // PythonQt::priv()->removeSignalEmitter() _self->removeSignalHandlers(); delete _self; _self = nullptr; } } PythonQt* PythonQt::self() { return _self; } PythonQt::PythonQt(int flags, const QByteArray& pythonQtModuleName) { _p = new PythonQtPrivate; _p->_initFlags = flags; if ((flags & PythonAlreadyInitialized) == 0) { Py_SetProgramName(const_cast(L"PythonQt")); if (flags & IgnoreSiteModule) { // this prevents the automatic importing of Python site files Py_NoSiteFlag = 1; } Py_Initialize(); } // add our own python object types for qt object slots if (PyType_Ready(&PythonQtSlotFunction_Type) < 0) { std::cerr << "could not initialize PythonQtSlotFunction_Type" << ", in " << __FILE__ << ":" << __LINE__ << std::endl; } Py_INCREF(&PythonQtSlotFunction_Type); if (PyType_Ready(&PythonQtSignalFunction_Type) < 0) { std::cerr << "could not initialize PythonQtSignalFunction_Type" << ", in " << __FILE__ << ":" << __LINE__ << std::endl; } Py_INCREF(&PythonQtSignalFunction_Type); if (PyType_Ready(&PythonQtSlotDecorator_Type) < 0) { std::cerr << "could not initialize PythonQtSlotDecorator_Type" << ", in " << __FILE__ << ":" << __LINE__ << std::endl; } Py_INCREF(&PythonQtSlotDecorator_Type); if (PyType_Ready(&PythonQtProperty_Type) < 0) { std::cerr << "could not initialize PythonQtProperty_Type" << ", in " << __FILE__ << ":" << __LINE__ << std::endl; } Py_INCREF(&PythonQtProperty_Type); PythonQtBoolResult_Type.tp_new = PyType_GenericNew; if (PyType_Ready(&PythonQtBoolResult_Type) < 0) { std::cerr << "could not initialize PythonQtBoolResult_Type" << ", in " << __FILE__ << ":" << __LINE__ << std::endl; } Py_INCREF(&PythonQtBoolResult_Type); // according to Python docs, set the type late here, since it can not safely be stored in the struct when declaring it PythonQtClassWrapper_Type.tp_base = &PyType_Type; // add our own python object types for classes if (PyType_Ready(&PythonQtClassWrapper_Type) < 0) { std::cerr << "could not initialize PythonQtClassWrapper_Type" << ", in " << __FILE__ << ":" << __LINE__ << std::endl; } Py_INCREF(&PythonQtClassWrapper_Type); // add our own python object types for CPP instances if (PyType_Ready(&PythonQtInstanceWrapper_Type) < 0) { PythonQt::handleError(); std::cerr << "could not initialize PythonQtInstanceWrapper_Type" << ", in " << __FILE__ << ":" << __LINE__ << std::endl; } Py_INCREF(&PythonQtInstanceWrapper_Type); // add our own python object types for redirection of stdout if (PyType_Ready(&PythonQtStdOutRedirectType) < 0) { std::cerr << "could not initialize PythonQtStdOutRedirectType" << ", in " << __FILE__ << ":" << __LINE__ << std::endl; } Py_INCREF(&PythonQtStdOutRedirectType); // add our own python object types for redirection of stdin if (PyType_Ready(&PythonQtStdInRedirectType) < 0) { std::cerr << "could not initialize PythonQtStdInRedirectType" << ", in " << __FILE__ << ":" << __LINE__ << std::endl; } Py_INCREF(&PythonQtStdInRedirectType); initPythonQtModule(flags & RedirectStdOut, pythonQtModuleName); } PythonQt::~PythonQt() { delete _p; _p = nullptr; } PythonQtPrivate::~PythonQtPrivate() { delete _defaultImporter; _defaultImporter = nullptr; PythonQtClassInfo::clearGlobalNamespaceWrappers(); qDeleteAll(_knownClassInfos); _knownClassInfos.clear(); PythonQtMethodInfo::cleanupCachedMethodInfos(); PythonQtArgumentFrame::cleanupFreeList(); } void PythonQtPrivate::setTaskDoneCallback(const PythonQtObjectPtr& callable) { _pyTaskDoneCallback = callable; } PythonQtObjectPtr PythonQtPrivate::checkAndRunCoroutine(const PythonQtObjectPtr& object) { PythonQtObjectPtr result; if (!PyCoro_CheckExact(object)) { return result; } if (!_pyEnsureFuture) { std::cerr << "PythonQt: ensure_future not initialized" << std::endl; return PythonQtObjectPtr(); } PyObject* args = PyTuple_New(1); PyObject* coro = object.object(); Py_INCREF(coro); PyTuple_SetItem(args, 0, coro); PyObject* r = PyObject_CallObject(_pyEnsureFuture, args); result.setNewRef(r); if (_pyTaskDoneCallback) { PyObject* methodName = PyUnicode_FromString("add_done_callback"); PyObject_CallMethodObjArgs(r, methodName, _pyTaskDoneCallback.object(), nullptr); Py_XDECREF(methodName); } Py_XDECREF(args); return result; } PythonQtObjectPtr PythonQtPrivate::createAsyncioFuture() { if (!_pyFutureClass) { std::cerr << "PythonQt: _pyFutureClass not initialized" << std::endl; return nullptr; } return _pyFutureClass.call(); } void PythonQt::setRedirectStdInCallback(PythonQtInputChangedCB* callback, void* callbackData) { if (!callback) { std::cerr << "PythonQt::setRedirectStdInCallback - callback parameter is NULL !" << std::endl; return; } PythonQtObjectPtr sys; PythonQtObjectPtr in; sys.setNewRef(PyImport_ImportModule("sys")); // Backup original 'sys.stdin' if not yet done if (!PyObject_HasAttrString(sys.object(), "pythonqt_original_stdin")) { PyObject_SetAttrString(sys.object(), "pythonqt_original_stdin", PyObject_GetAttrString(sys.object(), "stdin")); } in = PythonQtStdInRedirectType.tp_new(&PythonQtStdInRedirectType, nullptr, nullptr); ((PythonQtStdInRedirect*)in.object())->_cb = callback; ((PythonQtStdInRedirect*)in.object())->_callData = callbackData; // replace the built in file objects with our own objects PyModule_AddObject(sys.object(), "stdin", in); // Backup custom 'stdin' into 'pythonqt_stdin' Py_INCREF(in); // AddObject steals the reference, so increment it PyModule_AddObject(sys.object(), "pythonqt_stdin", in); } void PythonQt::setRedirectStdInCallbackEnabled(bool enabled) { PythonQtObjectPtr sys; sys.setNewRef(PyImport_ImportModule("sys")); if (enabled) { if (PyObject_HasAttrString(sys.object(), "pythonqt_stdin")) { PyObject_SetAttrString(sys.object(), "stdin", PyObject_GetAttrString(sys.object(), "pythonqt_stdin")); } } else { if (PyObject_HasAttrString(sys.object(), "pythonqt_original_stdin")) { PyObject_SetAttrString(sys.object(), "stdin", PyObject_GetAttrString(sys.object(), "pythonqt_original_stdin")); } } } PythonQtImportFileInterface* PythonQt::importInterface() { return _self->_p->_importInterface ? _self->_p->_importInterface : _self->_p->_defaultImporter; } void PythonQt::qObjectNoLongerWrappedCB(QObject* o) { if (_self->_p->_noLongerWrappedCB) { (*_self->_p->_noLongerWrappedCB)(o); } } void PythonQt::setQObjectMissingAttributeCallback(PythonQtQObjectMissingAttributeCB* cb) { _self->_p->_qObjectMissingAttribCB = cb; } QString PythonQt::qObjectMissingAttributeCallback(QObject* o, const QString& attribute) { if (_self && _self->_p && _self->_p->_qObjectMissingAttribCB) { return _self->_p->_qObjectMissingAttribCB(o, attribute); } else { return QString(); } } void PythonQt::registerClass(const QMetaObject* metaobject, const char* package, PythonQtQObjectCreatorFunctionCB* wrapperCreator, PythonQtShellSetInstanceWrapperCB* shell) { _p->registerClass(metaobject, package, wrapperCreator, shell); } void PythonQtPrivate::registerClass(const QMetaObject* metaobject, const char* package, PythonQtQObjectCreatorFunctionCB* wrapperCreator, PythonQtShellSetInstanceWrapperCB* shell, PyObject* module, int typeSlots) { // we register all classes in the hierarchy const QMetaObject* m = metaobject; bool first = true; while (m) { PythonQtClassInfo* info = lookupClassInfoAndCreateIfNotPresent(m->className()); if (!info->pythonQtClassWrapper()) { info->setTypeSlots(typeSlots); info->setupQObject(m); createPythonQtClassWrapper(info, package, module); if (m->superClass()) { PythonQtClassInfo* parentInfo = lookupClassInfoAndCreateIfNotPresent(m->superClass()->className()); info->addParentClass(PythonQtClassInfo::ParentClassInfo(parentInfo)); } } else if (first && (module || (package && package[0]))) { // There is a wrapper already, but if we got a module, we want to place the wrapper into that module as well, // since it might have been placed into "private" earlier on. // If the wrapper was already added to module before, it is just readded, which does no harm. PyObject* classWrapper = info->pythonQtClassWrapper(); // AddObject steals a reference, so we need to INCREF Py_INCREF(classWrapper); PyObject* pack = module ? module : packageByName(package); // same logic like in createPythonQtClassWrapper if (PyModule_AddObject(pack, info->className(), classWrapper) < 0) { Py_DECREF(classWrapper); } } if (first) { first = false; if (wrapperCreator) { info->setDecoratorProvider(wrapperCreator); } if (shell) { info->setShellSetInstanceWrapperCB(shell); } } m = m->superClass(); } } void PythonQtPrivate::createPythonQtClassWrapper(PythonQtClassInfo* info, const char* package, PyObject* module) { QByteArray pythonClassName = info->className(); int nestedClassIndex = pythonClassName.indexOf("::"); bool isNested = false; if (nestedClassIndex > 0) { pythonClassName = pythonClassName.mid(nestedClassIndex + 2); isNested = true; } PyObject* pack = module ? module : packageByName(package); PyObject* pyobj = (PyObject*)createNewPythonQtClassWrapper(info, pack, pythonClassName); if (isNested) { QByteArray outerClass = QByteArray(info->className()).mid(0, nestedClassIndex); PythonQtClassInfo* outerClassInfo = lookupClassInfoAndCreateIfNotPresent(outerClass); outerClassInfo->addNestedClass(info); } else { if (PyModule_AddObject(pack, info->className(), pyobj) == 0) { // since PyModule_AddObject steals the reference, we need a incref once more... Py_INCREF(pyobj); } } if (!module && package && strncmp(package, "Qt", 2) == 0) { // put all qt objects into Qt as well if (PyModule_AddObject(packageByName("Qt"), info->className(), pyobj) == 0) { // since PyModule_AddObject steals the reference, we need a incref once more... Py_INCREF(pyobj); } } info->setPythonQtClassWrapper(pyobj); } PyObject* PythonQtPrivate::wrapQObject(QObject* obj) { if (!obj) { Py_RETURN_NONE; } PythonQtInstanceWrapper* wrap = findWrapperAndRemoveUnused(obj); if (wrap && wrap->_wrappedPtr) { // uh oh, we want to wrap a QObject, but have a C++ wrapper at that // address, so probably that C++ wrapper has been deleted earlier and // now we see a QObject with the same address. // Do not use the old wrapper anymore. wrap = nullptr; } if (!wrap) { // smuggling it in... PythonQtClassInfo* classInfo = _knownClassInfos.value(obj->metaObject()->className()); if (!classInfo || classInfo->pythonQtClassWrapper() == nullptr) { registerClass(obj->metaObject()); classInfo = _knownClassInfos.value(obj->metaObject()->className()); } wrap = createNewPythonQtInstanceWrapper(obj, classInfo); // mlabDebugConst("MLABPython","new qobject wrapper added " << " " << wrap->_obj->className() << " " << wrap->classInfo()->wrappedClassName().latin1()); } else { Py_INCREF(wrap); // mlabDebugConst("MLABPython","qobject wrapper reused " << wrap->_obj->className() << " " << wrap->classInfo()->wrappedClassName().latin1()); } return (PyObject*)wrap; } PyObject* PythonQtPrivate::wrapPtr(void* ptr, const QByteArray& name, bool passOwnership) { if (!ptr) { Py_RETURN_NONE; } PythonQtInstanceWrapper* wrap = findWrapperAndRemoveUnused(ptr); PythonQtInstanceWrapper* possibleStillAliveWrapper = nullptr; if (wrap && wrap->_wrappedPtr) { // we have a previous C++ wrapper... if the wrapper is for a C++ object, // we are not sure if it may have been deleted earlier and we just see the same C++ // pointer once again. To make sure that we do not reuse a wrapper of the wrong type, // we compare the classInfo() pointer and only reuse the wrapper if it has the same // info. This is only needed for non-QObjects, since we know it when a QObject gets deleted. possibleStillAliveWrapper = wrap; wrap = nullptr; } if (!wrap) { PythonQtClassInfo* info = getClassInfo(name); if (!info) { // maybe it is a PyObject, which we can return directly if (name == "PyObject") { // do not increment its ref-count, it is the job of the slot returning the value // to ensure an extra ref on return. return (PyObject*)ptr; } // we do not know the metaobject yet, but we might know it by its name: if (_knownQObjectClassNames.find(name) != _knownQObjectClassNames.end()) { // yes, we know it, so we can convert to QObject QObject* qptr = (QObject*)ptr; registerClass(qptr->metaObject()); info = _knownClassInfos.value(qptr->metaObject()->className()); } } if (info && info->isQObject()) { QObject* qptr = (QObject*)ptr; // if the object is a derived object, we want to switch the class info to the one of the derived class: if (name != (qptr->metaObject()->className())) { info = _knownClassInfos.value(qptr->metaObject()->className()); if (!info || (info->pythonQtClassWrapper() == nullptr)) { registerClass(qptr->metaObject()); info = _knownClassInfos.value(qptr->metaObject()->className()); } } wrap = createNewPythonQtInstanceWrapper(qptr, info); wrap->_ownedByPythonQt = passOwnership; // mlabDebugConst("MLABPython","new qobject wrapper added " << " " << wrap->_obj->className() << " " << wrap->classInfo()->wrappedClassName().latin1()); return (PyObject*)wrap; } // not a known QObject, try to wrap via foreign wrapper factories PyObject* foreignWrapper = nullptr; for (int i = 0; i < _foreignWrapperFactories.size(); i++) { foreignWrapper = _foreignWrapperFactories.at(i)->wrap(name, ptr); if (foreignWrapper) { return foreignWrapper; } } // not a known QObject, so try our wrapper factory: QObject* wrapper = nullptr; for (int i = 0; i < _cppWrapperFactories.size(); i++) { wrapper = _cppWrapperFactories.at(i)->create(name, ptr); if (wrapper) { break; } } if (info) { // try to downcast in the class hierarchy, which will modify info and ptr if it is successfull ptr = info->castDownIfPossible(ptr, &info); // if downcasting found out that the object is a QObject, // handle it like one: if (info && info->isQObject()) { QObject* qptr = (QObject*)ptr; // if the object is a derived object, we want to switch the class info to the one of the derived class: if (name != (qptr->metaObject()->className())) { registerClass(qptr->metaObject()); info = _knownClassInfos.value(qptr->metaObject()->className()); } wrap = createNewPythonQtInstanceWrapper(qptr, info); wrap->_ownedByPythonQt = passOwnership; // mlabDebugConst("MLABPython","new qobject wrapper added " << " " << wrap->_obj->className() << " " << wrap->classInfo()->wrappedClassName().latin1()); return (PyObject*)wrap; } } if (!info || info->pythonQtClassWrapper() == nullptr) { // still unknown, register as CPP class registerCPPClass(name.constData()); info = _knownClassInfos.value(name); } if (wrapper && (info->metaObject() != wrapper->metaObject())) { // if we a have a QObject wrapper and the metaobjects do not match, set the metaobject again! info->setMetaObject(wrapper->metaObject()); } if (possibleStillAliveWrapper && possibleStillAliveWrapper->classInfo()->inherits(info)) { wrap = possibleStillAliveWrapper; Py_INCREF(wrap); } else { wrap = createNewPythonQtInstanceWrapper(wrapper, info, ptr); wrap->_ownedByPythonQt = passOwnership; } // mlabDebugConst("MLABPython","new c++ wrapper added " << wrap->_wrappedPtr << " " << wrap->_obj->className() << " " << wrap->classInfo()->wrappedClassName().latin1()); } else { Py_INCREF(wrap); //mlabDebugConst("MLABPython","c++ wrapper reused " << wrap->_wrappedPtr << " " << wrap->_obj->className() << " " << wrap->classInfo()->wrappedClassName().latin1()); } return (PyObject*)wrap; } PyObject* PythonQtPrivate::dummyTuple() { static PyObject* dummyTuple = nullptr; if (dummyTuple == nullptr) { dummyTuple = PyTuple_New(1); PyTuple_SET_ITEM(dummyTuple, 0, PyUnicode_FromString("dummy")); } return dummyTuple; } PythonQtInstanceWrapper* PythonQtPrivate::createNewPythonQtInstanceWrapper(QObject* obj, PythonQtClassInfo* info, void* wrappedPtr) { // call the associated class type to create a new instance... PythonQtInstanceWrapper* result = (PythonQtInstanceWrapper*)PyObject_Call(info->pythonQtClassWrapper(), dummyTuple(), nullptr); result->setQObject(obj); result->_wrappedPtr = wrappedPtr; result->_ownedByPythonQt = false; result->_useQMetaTypeDestroy = false; if (wrappedPtr || obj) { // if this object is reference counted, we ref it: PythonQtVoidPtrCB* refCB = info->referenceCountingRefCB(); if (refCB) { (*refCB)(wrappedPtr); } if (wrappedPtr) { _wrappedObjects.insert(wrappedPtr, result); } else { _wrappedObjects.insert(obj, result); if (obj->parent() == nullptr && _wrappedCB) { // tell someone who is interested that the qobject is wrapped the first time, if it has no parent (*_wrappedCB)(obj); } } } return result; } PythonQtClassWrapper* PythonQtPrivate::createNewPythonQtClassWrapper(PythonQtClassInfo* info, PyObject* parentModule, const QByteArray& pythonClassName) { PythonQtClassWrapper* result; PyObject* className = PyUnicode_FromString(pythonClassName.constData()); PyObject* baseClasses = PyTuple_New(1); Py_INCREF((PyObject*)&PythonQtInstanceWrapper_Type); PyTuple_SET_ITEM(baseClasses, 0, (PyObject*)&PythonQtInstanceWrapper_Type); PyObject* typeDict = PyDict_New(); PyObject* moduleName = PyObject_GetAttrString(parentModule, "__name__"); PyDict_SetItemString(typeDict, "__module__", moduleName); PyObject* args = Py_BuildValue("OOO", className, baseClasses, typeDict); // set the class info so that PythonQtClassWrapper_new can read it _currentClassInfoForClassWrapperCreation = info; // create the new type object by calling the type result = (PythonQtClassWrapper*)PyObject_Call((PyObject*)&PythonQtClassWrapper_Type, args, nullptr); Py_DECREF(baseClasses); Py_DECREF(typeDict); Py_DECREF(moduleName); Py_DECREF(args); Py_DECREF(className); return result; } PyObject* PythonQtPrivate::createEnumValueInstance(PyObject* enumType, unsigned int enumValue) { PyObject* args = Py_BuildValue("(i)", enumValue); PyObject* result = PyObject_Call(enumType, args, nullptr); Py_DECREF(args); return result; } PyObject* PythonQtPrivate::createNewPythonQtEnumWrapper(const char* enumName, PyObject* parentObject) { PyObject* result; PyObject* className = PyUnicode_FromString(enumName); PyObject* baseClasses = PyTuple_New(1); Py_INCREF(&PyLong_Type); PyTuple_SET_ITEM(baseClasses, 0, (PyObject*)&PyLong_Type); PyObject* module = PyObject_GetAttrString(parentObject, "__module__"); PyObject* typeDict = PyDict_New(); PyDict_SetItemString(typeDict, "__module__", module); PyObject* args = Py_BuildValue("OOO", className, baseClasses, typeDict); // create the new int derived type object by calling the core type result = PyObject_Call((PyObject*)&PyType_Type, args, nullptr); Py_DECREF(baseClasses); Py_DECREF(module); Py_DECREF(typeDict); Py_DECREF(args); Py_DECREF(className); return result; } PythonQtSignalReceiver* PythonQt::getSignalReceiver(QObject* obj) { PythonQtSignalReceiver* r = _p->_signalReceivers[obj]; if (!r) { r = new PythonQtSignalReceiver(obj); _p->_signalReceivers.insert(obj, r); } return r; } bool PythonQt::addSignalHandler(QObject* obj, const char* signal, PyObject* module, const QString& objectname) { bool flag = false; PythonQtObjectPtr callable = lookupCallable(module, objectname); if (callable) { PythonQtSignalReceiver* r = getSignalReceiver(obj); flag = r->addSignalHandler(signal, callable); if (!flag) { // signal not found } } else { // callable not found } return flag; } bool PythonQt::addSignalHandler(QObject* obj, const char* signal, PyObject* receiver) { bool flag = false; PythonQtSignalReceiver* r = getSignalReceiver(obj); if (r) { flag = r->addSignalHandler(signal, receiver); } return flag; } bool PythonQt::removeSignalHandler(QObject* obj, const char* signal, PyObject* module, const QString& objectname) { bool flag = false; PythonQtObjectPtr callable = lookupCallable(module, objectname); if (callable) { PythonQtSignalReceiver* r = _p->_signalReceivers[obj]; if (r) { flag = r->removeSignalHandler(signal, callable); } } else { // callable not found } return flag; } bool PythonQt::removeSignalHandler(QObject* obj, const char* signal, PyObject* receiver) { bool flag = false; PythonQtSignalReceiver* r = _p->_signalReceivers[obj]; if (r) { flag = r->removeSignalHandler(signal, receiver); } return flag; } PythonQtObjectPtr PythonQt::lookupCallable(PyObject* module, const QString& name) { PythonQtObjectPtr p = lookupObject(module, name); if (p) { if (PyCallable_Check(p)) { return p; } } PyErr_Clear(); return nullptr; } PythonQtObjectPtr PythonQt::lookupObject(PyObject* module, const QString& name) { QStringList l = name.split('.'); PythonQtObjectPtr p = module; PythonQtObjectPtr prev; QByteArray b; for (QStringList::ConstIterator i = l.begin(); i != l.end() && p; ++i) { prev = p; b = QStringToPythonEncoding(*i); if (PyDict_Check(p)) { p = PyDict_GetItemString(p, b.data()); } else { p.setNewRef(PyObject_GetAttrString(p, b.data())); } } PyErr_Clear(); return p; } PythonQtObjectPtr PythonQt::getMainModule() { //both borrowed PythonQtObjectPtr dict = PyImport_GetModuleDict(); return PyDict_GetItemString(dict, "__main__"); } PythonQtObjectPtr PythonQt::importModule(const QString& name) { PythonQtObjectPtr mod; mod.setNewRef(PyImport_ImportModule(QStringToPythonCharPointer(name))); return mod; } QVariant PythonQt::evalCode(PyObject* object, PyObject* pycode) { QVariant result; clearError(); if (pycode) { PyObject* dict = nullptr; PyObject* globals = nullptr; if (PyModule_Check(object)) { dict = PyModule_GetDict(object); globals = dict; } else if (PyDict_Check(object)) { dict = object; globals = dict; } else { dict = PyObject_GetAttrString(object, "__dict__"); globals = PyObject_GetAttrString( PyImport_ImportModule(PyUnicode_AsUTF8(PyObject_GetAttrString(object, "__module__"))), "__dict__"); } PyObject* r = nullptr; if (dict) { r = PyEval_EvalCode(pycode, globals, dict); } if (r) { result = PythonQtConv::PyObjToQVariant(r); Py_DECREF(r); } else { handleError(); } } else { handleError(); } return result; } QVariant PythonQt::evalScript(PyObject* object, const QString& script, int start) { QVariant result; PythonQtObjectPtr p; PyObject* dict = nullptr; clearError(); if (PyModule_Check(object)) { dict = PyModule_GetDict(object); } else if (PyDict_Check(object)) { dict = object; } if (dict) { p.setNewRef(PyRun_String(QStringToPythonCharPointer(script), start, dict, dict)); } if (p) { result = PythonQtConv::PyObjToQVariant(p); } else { handleError(); } return result; } QVariant PythonQt::evalScript(const QString& script, PyObject* globals, PyObject* locals, int start) { QVariant result; PythonQtObjectPtr p; clearError(); if (globals) { p.setNewRef(PyRun_String(QStringToPythonCharPointer(script), start, globals, locals ? locals : globals)); if (p) { result = PythonQtConv::PyObjToQVariant(p); } else { handleError(); } } return result; } void PythonQt::evalFile(PyObject* module, const QString& filename) { // NOTE: error checking is done by parseFile and evalCode PythonQtObjectPtr code = parseFile(filename); if (code) { evalCode(module, code); } } PythonQtObjectPtr PythonQt::parseFile(const QString& filename) { PythonQtObjectPtr p; p.setNewRef(PythonQtImport::getCodeFromPyc(filename)); clearError(); if (!p) { handleError(); _p->_hadError = true; } return p; } PythonQtObjectPtr PythonQt::parseFileWithPythonLoaders(const QString& file) { PythonQtObjectPtr result; QString filename = file; PyObject* loaderClass = nullptr; if (QFile::exists(filename)) { loaderClass = _p->_pySourceFileLoader; } else { filename += "c"; if (QFile::exists(filename)) { loaderClass = _p->_pySourcelessFileLoader; } } if (loaderClass) { static QString dummy("x"); PythonQtObjectPtr loader; QVariantList args; args << dummy << filename; loader.setNewRef(callAndReturnPyObject(loaderClass, args)); QVariantList args2; args2 << dummy; PythonQtObjectPtr getCode; getCode.setNewRef(PyObject_GetAttrString(loader, "get_code")); result.setNewRef(callAndReturnPyObject(getCode, args2)); if (!result) { // we don't want frozenlib to appear in the stack: handleError(/* printStack = */ false); } } return result; } PythonQtObjectPtr PythonQt::createModuleFromFile(const QString& name, const QString& filename) { PythonQtObjectPtr code = parseFile(filename); PythonQtObjectPtr module = _p->createModule(name, code); return module; } PythonQtObjectPtr PythonQt::createModuleFromScript(const QString& name, const QString& script) { PyErr_Clear(); QString scriptCode = script; if (scriptCode.isEmpty()) { // we always need at least a linefeed scriptCode = "\n"; } PythonQtObjectPtr pycode; pycode.setNewRef(Py_CompileString(QStringToPythonCharPointer(scriptCode), "", Py_file_input)); PythonQtObjectPtr module = _p->createModule(name, pycode); return module; } PythonQtObjectPtr PythonQt::createUniqueModule() { static QString pyQtStr("PythonQt_module"); QString moduleName = pyQtStr + QString::number(_uniqueModuleCount++); return createModuleFromScript(moduleName); } void PythonQt::addObject(PyObject* object, const QString& name, QObject* qObject) { if (PyModule_Check(object)) { auto pyobj = _p->wrapQObject(qObject); if (PyModule_AddObject(object, QStringToPythonCharPointer(name), pyobj) < 0) { Py_DECREF(pyobj); } } else if (PyDict_Check(object)) { PyDict_SetItemString(object, QStringToPythonCharPointer(name), _p->wrapQObject(qObject)); } else { PyObject_SetAttrString(object, QStringToPythonCharPointer(name), _p->wrapQObject(qObject)); } } void PythonQt::addVariable(PyObject* object, const QString& name, const QVariant& v) { if (PyModule_Check(object)) { auto pyobj = PythonQtConv::QVariantToPyObject(v); if (PyModule_AddObject(object, QStringToPythonCharPointer(name), pyobj) < 0) { Py_DECREF(pyobj); } } else if (PyDict_Check(object)) { PyDict_SetItemString(object, QStringToPythonCharPointer(name), PythonQtConv::QVariantToPyObject(v)); } else { PyObject_SetAttrString(object, QStringToPythonCharPointer(name), PythonQtConv::QVariantToPyObject(v)); } } void PythonQt::removeVariable(PyObject* object, const QString& name) { if (PyDict_Check(object)) { PyDict_DelItemString(object, QStringToPythonCharPointer(name)); } else { PyObject_DelAttrString(object, QStringToPythonCharPointer(name)); } } QVariant PythonQt::getVariable(PyObject* object, const QString& objectname) { QVariant result; PythonQtObjectPtr obj = lookupObject(object, objectname); if (obj) { result = PythonQtConv::PyObjToQVariant(obj); } return result; } QVariant PythonQt::getNativeVariable(PyObject* object, const QString& objectname) { QVariant result; PythonQtObjectPtr obj = lookupObject(object, objectname); if (obj) { result = obj.toVariant(); } return result; } QStringList PythonQt::introspection(PyObject* module, const QString& objectname, PythonQt::ObjectType type) { QStringList results; PythonQtObjectPtr object; if (objectname.isEmpty()) { object = module; } else { object = lookupObject(module, objectname); if (!object && type == CallOverloads) { PyObject* dict = lookupObject(module, "__builtins__"); if (dict) { object = PyDict_GetItemString(dict, QStringToPythonCharPointer(objectname)); } } } if (object) { results = introspectObject(object, type); } PyErr_Clear(); return results; } QStringList PythonQt::introspectObject(PyObject* object, ObjectType type) { QStringList results; if (type == CallOverloads) { if (PythonQtSlotFunction_Check(object)) { PythonQtSlotFunctionObject* o = (PythonQtSlotFunctionObject*)object; results = o->m_ml->overloads(); } else if (PythonQtSignalFunction_Check(object)) { PythonQtSignalFunctionObject* o = (PythonQtSignalFunctionObject*)object; results = o->m_ml->overloads(); } else if (object->ob_type == &PythonQtClassWrapper_Type) { PythonQtClassWrapper* o = (PythonQtClassWrapper*)object; PythonQtSlotInfo* info = o->classInfo()->constructors(); if (info) { results = info->overloads(/*skipReturnValue = */ true); } } else { QString signature = _p->getSignature(object); if (!signature.isEmpty()) { results << signature; } else { PyObject* doc = PyObject_GetAttrString(object, "__doc__"); if (doc) { QString docString = QString::fromUtf8(PyUnicode_AsUTF8(doc)); Py_DECREF(doc); int idx = docString.indexOf("\n"); if (idx != -1) { docString = docString.mid(0, idx); } // if the first line contains a "(", take it as a signature if (docString.contains("(")) { results << docString; } } } } } else { PyObject* keys = nullptr; bool isDict = false; if (PyDict_Check(object)) { keys = PyDict_Keys(object); isDict = true; } else { keys = PyObject_Dir(object); } if (keys) { int count = PyList_Size(keys); PyObject* key; PyObject* value; QString keystr; for (int i = 0; i < count; i++) { key = PyList_GetItem(keys, i); if (isDict) { value = PyDict_GetItem(object, key); Py_INCREF(value); } else { value = PyObject_GetAttr(object, key); } if (!value) continue; keystr = PyUnicode_AsUTF8(key); static const QString underscoreStr("__tmp"); if (!keystr.startsWith(underscoreStr)) { switch (type) { case Anything: results << keystr; break; case Class: if (PythonQtUtils::isPythonClassType(value)) { results << keystr; } break; case Variable: if (value->ob_type != &PyCFunction_Type && value->ob_type != &PyFunction_Type && value->ob_type != &PyMethod_Type && value->ob_type != &PyModule_Type && value->ob_type != &PyType_Type && value->ob_type != &PythonQtSlotFunction_Type) { results << keystr; } break; case Function: if (value->ob_type == &PyCFunction_Type || value->ob_type == &PyFunction_Type || value->ob_type == &PyMethod_Type || value->ob_type == &PythonQtSlotFunction_Type) { results << keystr; } break; case Module: if (value->ob_type == &PyModule_Type) { results << keystr; } break; default: std::cerr << "PythonQt: introspection: unknown case" << ", in " << __FILE__ << ":" << __LINE__ << std::endl; } } Py_DECREF(value); } Py_DECREF(keys); } } PyErr_Clear(); return results; } PyObject* PythonQt::getObjectByType(const QString& typeName) { PythonQtObjectPtr sys; sys.setNewRef(PyImport_ImportModule("sys")); PythonQtObjectPtr modules = lookupObject(sys, "modules"); Q_ASSERT(PyDict_Check(modules)); QStringList tmp = typeName.split("."); QString simpleTypeName = tmp.takeLast(); QString moduleName = tmp.join("."); PyObject* object = nullptr; PyObject* moduleObject = PyDict_GetItemString(modules, QStringToPythonCharPointer(moduleName)); if (moduleObject) { object = PyObject_GetAttrString(moduleObject, QStringToPythonCharPointer(simpleTypeName)); } if (!object) { moduleObject = PyDict_GetItemString(modules, "__builtin__"); if (moduleObject) { object = PyObject_GetAttrString(moduleObject, QStringToPythonCharPointer(simpleTypeName)); } } PyErr_Clear(); return object; } QStringList PythonQt::introspectType(const QString& typeName, ObjectType type) { QStringList results; PyObject* object = getObjectByType(typeName); if (!object) { // the last item may be a member, split it away and try again QStringList tmp = typeName.split("."); QString memberName = tmp.takeLast(); QString typeName; if (tmp.isEmpty()) { typeName = memberName; memberName.clear(); } else { typeName = tmp.join("."); } PyObject* typeObject = getObjectByType(typeName); if (typeObject) { object = PyObject_GetAttrString(typeObject, QStringToPythonCharPointer(memberName)); } } if (object) { results = introspectObject(object, type); Py_DECREF(object); } PyErr_Clear(); return results; } QVariant PythonQt::call(PyObject* object, const QString& name, const QVariantList& args, const QVariantMap& kwargs) { PythonQtObjectPtr callable = lookupCallable(object, name); if (callable) { return call(callable, args, kwargs); } else { return QVariant(); } } QVariant PythonQt::call(PyObject* callable, const QVariantList& args, const QVariantMap& kwargs) { QVariant r; PythonQtObjectPtr result; result.setNewRef(callAndReturnPyObject(callable, args, kwargs)); clearError(); if (result) { r = PythonQtConv::PyObjToQVariant(result); } else { PythonQt::self()->handleError(); } return r; } PyObject* PythonQt::callAndReturnPyObject(PyObject* callable, const QVariantList& args, const QVariantMap& kwargs) { PyObject* result = nullptr; if (callable) { bool err = false; PythonQtObjectPtr pargs; int count = args.size(); if ((count > 0) || (kwargs.count() > 0)) { // create empty tuple if kwargs are given pargs.setNewRef(PyTuple_New(count)); // transform QVariant arguments to Python for (int i = 0; i < count; i++) { PyObject* arg = PythonQtConv::QVariantToPyObject(args.at(i)); if (arg) { // steals reference, no unref PyTuple_SetItem(pargs, i, arg); } else { err = true; break; } } } if (!err) { if (kwargs.isEmpty()) { // do a direct call if we have no keyword arguments PyErr_Clear(); result = PyObject_CallObject(callable, pargs); } else { // convert keyword arguments to Python PythonQtObjectPtr pkwargs; pkwargs.setNewRef(PyDict_New()); QMapIterator it(kwargs); while (it.hasNext()) { it.next(); PyObject* arg = PythonQtConv::QVariantToPyObject(it.value()); if (arg) { PyDict_SetItemString(pkwargs, QStringToPythonCharPointer(it.key()), arg); } else { err = true; break; } } if (!err) { // call with arguments and keyword arguments PyErr_Clear(); result = PyObject_Call(callable, pargs, pkwargs); } } } } return result; } void PythonQt::addInstanceDecorators(QObject* o) { _p->addDecorators(o, PythonQtPrivate::InstanceDecorator); } void PythonQt::addClassDecorators(QObject* o) { _p->addDecorators(o, PythonQtPrivate::StaticDecorator | PythonQtPrivate::ConstructorDecorator | PythonQtPrivate::DestructorDecorator); } void PythonQt::addDecorators(QObject* o) { _p->addDecorators(o, PythonQtPrivate::AllDecorators); } void PythonQt::registerQObjectClassNames(const QStringList& names) { _p->registerQObjectClassNames(names); } void PythonQt::setImporter(PythonQtImportFileInterface* importInterface) { _p->_importInterface = importInterface; PythonQtImport::init(); } void PythonQt::setImporterIgnorePaths(const QStringList& paths) { _p->_importIgnorePaths = paths; } const QStringList& PythonQt::getImporterIgnorePaths() { return _p->_importIgnorePaths; } void PythonQt::addWrapperFactory(PythonQtCppWrapperFactory* factory) { _p->_cppWrapperFactories.append(factory); } void PythonQt::addWrapperFactory(PythonQtForeignWrapperFactory* factory) { _p->_foreignWrapperFactories.append(factory); } //--------------------------------------------------------------------------------------------------- PythonQtPrivate::PythonQtPrivate() { _importInterface = nullptr; _defaultImporter = new PythonQtQFileImporter; _noLongerWrappedCB = nullptr; _wrappedCB = nullptr; _qObjectMissingAttribCB = nullptr; _currentClassInfoForClassWrapperCreation = nullptr; _profilingCB = nullptr; _hadError = false; _systemExitExceptionHandlerEnabled = false; _debugAPI = new PythonQtDebugAPI(this); _configAPI = new PythonQtConfigAPI(this); } void PythonQtPrivate::setupSharedLibrarySuffixes() { _sharedLibrarySuffixes.clear(); PythonQtObjectPtr imp; imp.setNewRef(PyImport_ImportModule("imp")); int cExtensionCode = imp.getVariable("C_EXTENSION").toInt(); QVariant result = imp.call("get_suffixes"); #ifdef __linux #ifdef _DEBUG // First look for shared libraries with the '_d' suffix in debug mode on Linux. // This is a workaround, because python does not append the '_d' suffix on Linux // and would always load the release library otherwise. _sharedLibrarySuffixes << "_d.so"; #endif #endif const QVariantList entries = result.toList(); for (const QVariant& entry : entries) { QVariantList suffixEntry = entry.toList(); if (suffixEntry.count() == 3) { int code = suffixEntry.at(2).toInt(); if (code == cExtensionCode) { _sharedLibrarySuffixes << suffixEntry.at(0).toString(); } } } } PythonQtClassInfo* PythonQtPrivate::currentClassInfoForClassWrapperCreation() { PythonQtClassInfo* info = _currentClassInfoForClassWrapperCreation; _currentClassInfoForClassWrapperCreation = nullptr; return info; } void PythonQtPrivate::addDecorators(QObject* o, int decoTypes) { if (o == nullptr) { return; } o->setParent(this); int numMethods = o->metaObject()->methodCount(); for (int i = 0; i < numMethods; i++) { QMetaMethod m = o->metaObject()->method(i); QByteArray signature = PythonQtUtils::methodName(m); if ((m.methodType() == QMetaMethod::Method || m.methodType() == QMetaMethod::Slot) && m.access() == QMetaMethod::Public) { if (signature.startsWith("new_")) { if ((decoTypes & ConstructorDecorator) == 0) continue; const PythonQtMethodInfo* info = PythonQtMethodInfo::getCachedMethodInfo(m, nullptr); if (info->parameters().at(0).pointerCount == 1) { QByteArray nameOfClass = signature.mid(4); nameOfClass.replace("__", "::"); PythonQtClassInfo* classInfo = lookupClassInfoAndCreateIfNotPresent(nameOfClass); PythonQtSlotInfo* newSlot = new PythonQtSlotInfo(nullptr, m, i, o, PythonQtSlotInfo::ClassDecorator); classInfo->addConstructor(newSlot); } } else if (signature.startsWith("delete_")) { if ((decoTypes & DestructorDecorator) == 0) continue; QByteArray nameOfClass = signature.mid(7); nameOfClass.replace("__", "::"); PythonQtClassInfo* classInfo = lookupClassInfoAndCreateIfNotPresent(nameOfClass); PythonQtSlotInfo* newSlot = new PythonQtSlotInfo(nullptr, m, i, o, PythonQtSlotInfo::ClassDecorator); classInfo->setDestructor(newSlot); } else if (signature.startsWith("static_")) { if ((decoTypes & StaticDecorator) == 0) continue; QByteArray nameOfClass = signature.mid(7); nameOfClass = nameOfClass.mid(0, nameOfClass.indexOf('_')); PythonQtClassInfo* classInfo = lookupClassInfoAndCreateIfNotPresent(nameOfClass); PythonQtSlotInfo* newSlot = new PythonQtSlotInfo(nullptr, m, i, o, PythonQtSlotInfo::ClassDecorator); classInfo->addDecoratorSlot(newSlot); } else { if ((decoTypes & InstanceDecorator) == 0) continue; const PythonQtMethodInfo* info = PythonQtMethodInfo::getCachedMethodInfo(m, nullptr); if (info->parameters().count() > 1) { PythonQtMethodInfo::ParameterInfo p = info->parameters().at(1); if (p.pointerCount == 1) { PythonQtClassInfo* classInfo = lookupClassInfoAndCreateIfNotPresent(p.name); PythonQtSlotInfo* newSlot = new PythonQtSlotInfo(nullptr, m, i, o, PythonQtSlotInfo::InstanceDecorator); classInfo->addDecoratorSlot(newSlot); } } } } } } void PythonQtPrivate::registerQObjectClassNames(const QStringList& names) { for (const QString& name : names) { _knownQObjectClassNames.insert(name.toUtf8(), true); } } void PythonQtPrivate::removeSignalEmitter(QObject* obj) { _signalReceivers.remove(obj); } void PythonQt::removeSignalHandlers() { QList signalReceivers = _p->_signalReceivers.values(); // just delete all signal receivers, they will remove themselves via removeSignalEmitter() for (PythonQtSignalReceiver* receiver : qAsConst(signalReceivers)) { delete receiver; } // just to be sure, clear the receiver map as well _p->_signalReceivers.clear(); } namespace { //! adapted from python source file "pythonrun.c", function "handle_system_exit" //! return the exitcode instead of calling "Py_Exit". //! it gives the application an opportunity to properly terminate. int custom_system_exit_exception_handler() { PyObject *exception, *value, *tb; int exitcode = 0; // if (Py_InspectFlag) // /* Don't exit if -i flag was given. This flag is set to 0 // * when entering interactive mode for inspecting. */ // return exitcode; PyErr_Fetch(&exception, &value, &tb); fflush(stdout); if (value == nullptr || value == Py_None) goto done; if (PyExceptionInstance_Check(value)) { /* The error code should be in the `code' attribute. */ PyObject* code = PyObject_GetAttrString(value, "code"); if (code) { Py_DECREF(value); value = code; if (value == Py_None) goto done; } /* If we failed to dig out the 'code' attribute, just let the else clause below print the error. */ } if (PyLong_Check(value)) exitcode = (int)PyLong_AsLong(value); else { PyObject* sys_stderr = PySys_GetObject(const_cast("stderr")); if (sys_stderr != nullptr && sys_stderr != Py_None) { PyFile_WriteObject(value, sys_stderr, Py_PRINT_RAW); } else { PyObject_Print(value, stderr, Py_PRINT_RAW); fflush(stderr); } PySys_WriteStderr("\n"); exitcode = 1; } done: /* Restore and clear the exception info, in order to properly decref * the exception, value, and traceback. If we just exit instead, * these leak, which confuses PYTHONDUMPREFS output, and may prevent * some finalizers from running. */ PyErr_Restore(exception, value, tb); PyErr_Clear(); return exitcode; //Py_Exit(exitcode); } } bool PythonQt::handleError(bool printStack) { bool flag = false; if (PyErr_Occurred()) { if (_p->_systemExitExceptionHandlerEnabled && PyErr_ExceptionMatches(PyExc_SystemExit)) { int exitcode = custom_system_exit_exception_handler(); Q_EMIT PythonQt::self()->systemExitExceptionRaised(exitcode); } else { if (printStack) { PyErr_Print(); } else { PyObject *ptype, *pvalue, *ptraceback; PyErr_Fetch(&ptype, &pvalue, &ptraceback); PyErr_NormalizeException(&ptype, &pvalue, &ptraceback); // we leave out the traceback: PyErr_Display(ptype, pvalue, nullptr); PyErr_Restore(ptype, pvalue, ptraceback); PyErr_Clear(); } flag = true; } } _p->_hadError = flag; return flag; } bool PythonQt::hadError() const { return _p->_hadError; } void PythonQt::clearError() { _p->_hadError = false; } void PythonQt::setSystemExitExceptionHandlerEnabled(bool value) { _p->_systemExitExceptionHandlerEnabled = value; } bool PythonQt::systemExitExceptionHandlerEnabled() const { return _p->_systemExitExceptionHandlerEnabled; } void PythonQt::addSysPath(const QString& path) { PythonQtObjectPtr sys; sys.setNewRef(PyImport_ImportModule("sys")); PythonQtObjectPtr obj = lookupObject(sys, "path"); PyList_Insert(obj, 0, PythonQtConv::QStringToPyObject(path)); } void PythonQt::overwriteSysPath(const QStringList& paths) { PythonQtObjectPtr sys; sys.setNewRef(PyImport_ImportModule("sys")); // Since Python uses os.path.sep at various places, // makse sure that we use the native path separators. QStringList nativePaths; for (const QString& path : paths) { nativePaths << QDir::toNativeSeparators(path); } PyModule_AddObject(sys, "path", PythonQtConv::QStringListToPyList(nativePaths)); } void PythonQt::setModuleImportPath(PyObject* module, const QStringList& paths) { PyModule_AddObject(module, "__path__", PythonQtConv::QStringListToPyList(paths)); #if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 4) // Since Python 3.4 a module has a __spec__ member of type ModuleSpec which // mirrors some module-specific attributes, see // https://docs.python.org/3/library/importlib.html#importlib.machinery.ModuleSpec . // // Python 3.9 prints an import warning "__package__ != __spec__.parent" on relative imports // if we don't set the submodule_search_locations on __spec__ (this indirectly changes // the value of parent) PyObject* spec = PyObject_GetAttrString(module, "__spec__"); if (spec) { PythonQt::self()->addVariable(spec, "submodule_search_locations", paths); Py_DECREF(spec); } #endif } void PythonQt::stdOutRedirectCB(const QString& str) { if (!PythonQt::self()) { std::cout << QStringToPythonConstCharPointer(str) << std::endl; return; } Q_EMIT PythonQt::self()->pythonStdOut(str); } void PythonQt::stdErrRedirectCB(const QString& str) { if (!PythonQt::self()) { std::cerr << QStringToPythonConstCharPointer(str) << std::endl; return; } Q_EMIT PythonQt::self()->pythonStdErr(str); } void PythonQt::setQObjectWrappedCallback(PythonQtQObjectWrappedCB* cb) { _p->_wrappedCB = cb; } void PythonQt::setQObjectNoLongerWrappedCallback(PythonQtQObjectNoLongerWrappedCB* cb) { _p->_noLongerWrappedCB = cb; } void PythonQt::setProfilingCallback(ProfilingCB* cb) { _p->_profilingCB = cb; } void PythonQt::setEnableThreadSupport(bool flag) { #ifdef PYTHONQT_FULL_THREAD_SUPPORT PythonQtGILScope::setGILScopeEnabled(flag); PythonQtSlotInfo::setGlobalShouldAllowThreads(flag); #endif } static PyMethodDef PythonQtMethods[] = {{nullptr, nullptr, 0, nullptr}}; static PyModuleDef PythonQtModuleDef = { PyModuleDef_HEAD_INIT, /* m_base */ "", /* m_name */ nullptr, /* m_doc */ -1, /* m_size */ PythonQtMethods, /* m_methods */ nullptr, /* m_slots */ nullptr, /* m_traverse */ nullptr, /* m_clear */ nullptr /* m_free */ }; void PythonQt::initPythonQtModule(bool redirectStdOut, const QByteArray& pythonQtModuleName) { QByteArray name = "PythonQt"; if (!pythonQtModuleName.isEmpty()) { name = pythonQtModuleName; } PythonQtModuleDef.m_name = name.constData(); _p->_pythonQtModule = PyModule_Create(&PythonQtModuleDef); _p->_pythonQtModuleName = name; Py_INCREF((PyObject*)&PythonQtBoolResult_Type); PyModule_AddObject(_p->pythonQtModule().object(), "BoolResult", (PyObject*)&PythonQtBoolResult_Type); PythonQtObjectPtr sys; sys.setNewRef(PyImport_ImportModule("sys")); // Backup original 'sys.stdout' and 'sys.stderr' PyModule_AddObject(sys, "pythonqt_original_stdout", PyObject_GetAttrString(sys, "stdout")); PyModule_AddObject(sys, "pythonqt_original_stderr", PyObject_GetAttrString(sys, "stderr")); // Create a redirection object for stdout and stderr PythonQtObjectPtr out; PythonQtObjectPtr err; out = PythonQtStdOutRedirectType.tp_new(&PythonQtStdOutRedirectType, NULL, NULL); ((PythonQtStdOutRedirect*)out.object())->_cb = stdOutRedirectCB; err = PythonQtStdOutRedirectType.tp_new(&PythonQtStdOutRedirectType, NULL, NULL); ((PythonQtStdOutRedirect*)err.object())->_cb = stdErrRedirectCB; // replace the built in file objects with our own objects PyModule_AddObject(sys, "pythonqt_stdout", out); PyModule_AddObject(sys, "pythonqt_stderr", err); setRedirectStdOutCallbackEnabled(redirectStdOut); // add PythonQt to the list of builtin module names PyObject* old_module_names = PyObject_GetAttrString(sys.object(), "builtin_module_names"); if (old_module_names && PyTuple_Check(old_module_names)) { Py_ssize_t old_size = PyTuple_Size(old_module_names); PyObject* module_names = PyTuple_New(old_size + 1); for (Py_ssize_t i = 0; i < old_size; i++) { PyObject* val = PyTuple_GetItem(old_module_names, i); Py_INCREF(val); PyTuple_SetItem(module_names, i, val); } PyTuple_SetItem(module_names, old_size, PyUnicode_FromString(name.constData())); PyModule_AddObject(sys.object(), "builtin_module_names", module_names); } Py_XDECREF(old_module_names); PyObject* modulesAttr = PyObject_GetAttrString(sys.object(), "modules"); PyObject* pyUnicodeObject = PyUnicode_FromString(name.constData()); PyDict_SetItem(modulesAttr, pyUnicodeObject, _p->_pythonQtModule.object()); Py_XDECREF(modulesAttr); Py_XDECREF(pyUnicodeObject); } bool PythonQt::redirectStdOutCallbackEnabled() const { PythonQtObjectPtr sys; sys.setNewRef(PyImport_ImportModule("sys")); PythonQtObjectPtr pythonqt_stdout; pythonqt_stdout.setNewRef(PyObject_GetAttrString(sys.object(), "pythonqt_stdout")); PythonQtObjectPtr sys_stdout; sys_stdout.setNewRef(PyObject_GetAttrString(sys.object(), "stdout")); return PyObject_RichCompareBool(pythonqt_stdout.object(), sys_stdout.object(), Py_EQ); } void PythonQt::setRedirectStdOutCallbackEnabled(bool enabled) { PythonQtObjectPtr sys; sys.setNewRef(PyImport_ImportModule("sys")); if (enabled) { if (PyObject_HasAttrString(sys.object(), "pythonqt_stdout")) { PyModule_AddObject(sys.object(), "stdout", PyObject_GetAttrString(sys.object(), "pythonqt_stdout")); } if (PyObject_HasAttrString(sys.object(), "pythonqt_stderr")) { PyModule_AddObject(sys.object(), "stderr", PyObject_GetAttrString(sys.object(), "pythonqt_stderr")); } } else { if (PyObject_HasAttrString(sys.object(), "pythonqt_original_stdout")) { PyModule_AddObject(sys.object(), "stdout", PyObject_GetAttrString(sys.object(), "pythonqt_original_stdout")); } if (PyObject_HasAttrString(sys.object(), "pythonqt_original_stderr")) { PyModule_AddObject(sys.object(), "stderr", PyObject_GetAttrString(sys.object(), "pythonqt_original_stderr")); } } } QString PythonQt::getReturnTypeOfWrappedMethod(PyObject* module, const QString& name) { QStringList tmp = name.split("."); QString methodName = tmp.takeLast(); QString variableName = tmp.join("."); PythonQtObjectPtr variableObject = lookupObject(module, variableName); if (variableObject.isNull()) { // try lookup by interpreting the variableName as a type QString type = getReturnTypeOfWrappedMethod(variableName, methodName); return type; } return getReturnTypeOfWrappedMethodHelper(variableObject, methodName, name); } QString PythonQt::getReturnTypeOfWrappedMethod(const QString& typeName, const QString& methodName) { PythonQtObjectPtr typeObject = getObjectByType(typeName); if (typeObject.isNull()) { return ""; } return getReturnTypeOfWrappedMethodHelper(typeObject, methodName, typeName + "." + methodName); } QString PythonQt::getReturnTypeOfWrappedMethodHelper(const PythonQtObjectPtr& variableObject, const QString& methodName, const QString& context) { PythonQtObjectPtr methodObject; if (PyDict_Check(variableObject)) { methodObject = PyDict_GetItemString(variableObject, QStringToPythonConstCharPointer(methodName)); } else { methodObject.setNewRef(PyObject_GetAttrString(variableObject, QStringToPythonConstCharPointer(methodName))); } if (methodObject.isNull()) { return ""; } QString type; if (PythonQtUtils::isPythonClassType(methodObject)) { // the methodObject is not a method, but the name of a type/class. This means // a constructor is called. Return the context. type = context; } else if (methodObject->ob_type == &PythonQtSlotFunction_Type) { PythonQtSlotInfo* slotInfo = ((PythonQtSlotFunctionObject*)methodObject.object())->m_ml; if (slotInfo) { if (slotInfo->parameterCount() > 0) { type = slotInfo->parameters().at(0).name; if (type.contains("<")) { // can't handle templates type = ""; } if (!type.isEmpty()) { // if the type is a known class info, then create the full type name, i.e. include the // module name. For example, the slot may return a QDate, then this looks up the // name _PythonQt.QtCore.QDate. PythonQtClassInfo* typeInfo = _p->_knownClassInfos.value(QStringToPythonConstCharPointer(type)); if (typeInfo && typeInfo->pythonQtClassWrapper()) { PyObject* s = PyObject_GetAttrString(typeInfo->pythonQtClassWrapper(), "__module__"); Q_ASSERT(PyUnicode_Check(s)); type = QString(PyUnicode_AsUTF8(s)) + "." + type; Py_DECREF(s); } } } } } return type; } void PythonQt::registerCPPClass(const char* typeName, const char* parentTypeName, const char* package, PythonQtQObjectCreatorFunctionCB* wrapperCreator, PythonQtShellSetInstanceWrapperCB* shell) { _p->registerCPPClass(typeName, parentTypeName, package, wrapperCreator, shell); } PythonQtClassInfo* PythonQtPrivate::lookupClassInfoAndCreateIfNotPresent(const char* typeName) { PythonQtClassInfo* info = _knownClassInfos.value(typeName); if (!info) { info = new PythonQtClassInfo(); info->setupCPPObject(typeName); _knownClassInfos.insert(typeName, info); } return info; } void PythonQt::addPolymorphicHandler(const char* typeName, PythonQtPolymorphicHandlerCB* cb) { _p->addPolymorphicHandler(typeName, cb); } void PythonQtPrivate::addPolymorphicHandler(const char* typeName, PythonQtPolymorphicHandlerCB* cb) { PythonQtClassInfo* info = lookupClassInfoAndCreateIfNotPresent(typeName); info->addPolymorphicHandler(cb); } bool PythonQt::addParentClass(const char* typeName, const char* parentTypeName, int upcastingOffset) { return _p->addParentClass(typeName, parentTypeName, upcastingOffset); } bool PythonQtPrivate::addParentClass(const char* typeName, const char* parentTypeName, int upcastingOffset) { PythonQtClassInfo* info = _knownClassInfos.value(typeName); if (info) { PythonQtClassInfo* parentInfo = lookupClassInfoAndCreateIfNotPresent(parentTypeName); info->addParentClass(PythonQtClassInfo::ParentClassInfo(parentInfo, upcastingOffset)); return true; } else { return false; } } void PythonQtPrivate::registerCPPClass(const char* typeName, const char* parentTypeName, const char* package, PythonQtQObjectCreatorFunctionCB* wrapperCreator, PythonQtShellSetInstanceWrapperCB* shell, PyObject* module, int typeSlots) { PythonQtClassInfo* info = lookupClassInfoAndCreateIfNotPresent(typeName); if (!info->pythonQtClassWrapper()) { info->setTypeSlots(typeSlots); info->setupCPPObject(typeName); createPythonQtClassWrapper(info, package, module); } if (parentTypeName && strcmp(parentTypeName, "") != 0) { addParentClass(typeName, parentTypeName, 0); } if (wrapperCreator) { info->setDecoratorProvider(wrapperCreator); } if (shell) { info->setShellSetInstanceWrapperCB(shell); } if (info->typeSlots() & PythonQt::Type_EnterExit) { // context handlers must be handled specially, because optimized accesses // cause that the __enter__ and __exit__ methods will not be found by // conventional means: PyObject* classObj = (PyObject*)info->pythonQtClassWrapper(); PyTypeObject* typeObj = (PyTypeObject*)classObj; // __enter__ and _exit__ must be inserted directly into the tp_dict, // otherwise they are not found by "with": PyObject* tp_dict = typeObj->tp_dict; PyObject* enterMethod = PyObject_GetAttrString(classObj, "__enter__"); if (enterMethod) { PyDict_SetItemString(tp_dict, "__enter__", enterMethod); Py_DECREF(enterMethod); } PyErr_Clear(); PyObject* exitMethod = PyObject_GetAttrString(classObj, "__exit__"); if (exitMethod) { PyDict_SetItemString(tp_dict, "__exit__", exitMethod); Py_DECREF(exitMethod); } PyErr_Clear(); // invalidate attribute cache for this type: // (as the search for the class methods otherwise cached methods as "not existing", and // we don't want to rely on the wrong empty entries being evicted from the cache by chance): typeObj->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG; } } namespace { void addObjectToPackage(PyObject* obj, const char* name, const char* packageName, PyObject* package) { if (PyModule_AddObject(package, name, obj) < 0) { Py_DECREF(obj); std::cerr << "failed to add " << name << " to " << packageName << "\n"; } } }; void PythonQtPrivate::registerGlobalNamespace(const char* typeName, const char* packageName, PythonQtQObjectCreatorFunctionCB* wrapperCreator, const QMetaObject& metaObject, PyObject* module) { registerCPPClass(typeName, "", packageName, wrapperCreator, nullptr, module, 0); PyObject* package = module ? module : PythonQt::priv()->packageByName(packageName); PythonQtClassInfo* classInfo = PythonQt::priv()->getClassInfo(typeName); PyObject* globalNamespace = classInfo->pythonQtClassWrapper(); // Collect the names of global methods QSet methodNames; for (int i = metaObject.methodOffset(); i < metaObject.methodCount(); i++) { methodNames.insert(metaObject.method(i).name()); } QByteArray staticPrefix = "static_" + QByteArray(typeName) + "_"; // every static method starts with this string for (auto name : methodNames) { if (name.startsWith(staticPrefix)) { // non-static methods wouldn't work (and should not exists) name = name.mid(staticPrefix.length()); PyObject* obj = PyObject_GetAttrString(globalNamespace, name.constData()); if (obj) { addObjectToPackage(obj, name, packageName, package); } else { std::cerr << "method not found " << name.constData() << " in " << typeName << std::endl; } } } // Global enums for (int i = metaObject.enumeratorOffset(); i < metaObject.enumeratorCount(); i++) { QMetaEnum metaEnum = metaObject.enumerator(i); PyObject* obj = PyObject_GetAttrString(globalNamespace, metaEnum.name()); if (obj) { addObjectToPackage(obj, metaEnum.name(), packageName, package); } else { std::cerr << "enum type not found " << metaEnum.name() << " in " << typeName << std::endl; } #if QT_VERSION > 0x050800 bool isScoped = metaEnum.isScoped(); #else bool isScoped = false; #endif if (!isScoped) { for (int j = 0; j < metaEnum.keyCount(); j++) { QByteArray key = PythonQtClassInfo::escapeReservedNames(metaEnum.key(j)); int value = metaEnum.value(j); PyObject* obj = PyLong_FromLong(value); addObjectToPackage(obj, key, packageName, package); } } } PythonQtClassInfo::addGlobalNamespaceWrapper(classInfo); } PyObject* PythonQtPrivate::packageByName(const char* name) { if (name == nullptr || name[0] == 0) { name = "private"; } PyObject* v = _packages.value(name); if (!v) { v = PyImport_AddModule((_pythonQtModuleName + "." + name).constData()); _packages.insert(name, v); // AddObject steals the reference, so increment it! if (PyModule_AddObject(_pythonQtModule, name, v) == 0) { Py_INCREF(v); } } return v; } void PythonQtPrivate::handleVirtualOverloadReturnError(const char* signature, const PythonQtMethodInfo* methodInfo, PyObject* result) { QString error = "Return value '" + PythonQtConv::PyObjGetString(result) + "' can not be converted to expected C++ type '" + methodInfo->parameters().at(0).name + "' as return value of virtual method " + signature; PyErr_SetString(PyExc_AttributeError, QStringToPythonCharPointer(error)); PythonQt::self()->handleError(); } PyObject* PythonQt::helpCalled(PythonQtClassInfo* info) { if (_p->_initFlags & ExternalHelp) { Q_EMIT pythonHelpRequest(QByteArray(info->className())); return Py_BuildValue(""); } else { return PyUnicode_FromString(QStringToPythonCharPointer(info->help())); } } void PythonQt::clearNotFoundCachedMembers() { for (PythonQtClassInfo* info : qAsConst(_p->_knownClassInfos)) { info->clearNotFoundCachedMembers(); } } void PythonQt::removeWrapperFactory(PythonQtCppWrapperFactory* factory) { _p->_cppWrapperFactories.removeAll(factory); } void PythonQt::removeWrapperFactory(PythonQtForeignWrapperFactory* factory) { _p->_foreignWrapperFactories.removeAll(factory); } void PythonQtPrivate::removeWrapperPointer(void* obj) { _wrappedObjects.remove(obj); } void PythonQtPrivate::addWrapperPointer(void* obj, PythonQtInstanceWrapper* wrapper) { _wrappedObjects.insert(obj, wrapper); } PythonQtInstanceWrapper* PythonQtPrivate::findWrapperAndRemoveUnused(void* obj) { PythonQtInstanceWrapper* wrap = _wrappedObjects.value(obj); if (wrap && !wrap->_wrappedPtr && wrap->_obj == nullptr) { // this is a wrapper whose QObject was already removed due to destruction // so the obj pointer has to be a new QObject with the same address... // we remove the old one and set the copy to NULL wrap->_objPointerCopy = nullptr; removeWrapperPointer(obj); wrap = nullptr; } return wrap; } PythonQtObjectPtr PythonQtPrivate::createModule(const QString& name, PyObject* pycode) { PythonQtObjectPtr result; PythonQt::self()->clearError(); if (pycode) { result.setNewRef(PyImport_ExecCodeModule(QStringToPythonCharPointer(name), pycode)); } else { PythonQt::self()->handleError(); } return result; } void* PythonQtPrivate::unwrapForeignWrapper(const QByteArray& classname, PyObject* obj) { void* foreignObject = nullptr; for (int i = 0; i < _foreignWrapperFactories.size(); i++) { foreignObject = _foreignWrapperFactories.at(i)->unwrap(classname, obj); if (foreignObject) { return foreignObject; } } return nullptr; } bool PythonQtPrivate::isMethodDescriptor(PyObject* object) const { // This implementation is the same as in inspect.ismethoddescriptor(), inspect.py. if (PyObject_HasAttrString(object, "__get__") && !PyObject_HasAttrString(object, "__set__") && !PyMethod_Check(object) && !PyFunction_Check(object)) { return true; } return false; } // We need this for the dynamic meta object building: #include const QMetaObject* PythonQtPrivate::getDynamicMetaObject(PythonQtInstanceWrapper* wrapper, const QMetaObject* prototypeMetaObject) { PYTHONQT_GIL_SCOPE; PythonQtDynamicClassInfo* info = wrapper->dynamicClassInfo(); if (info) { if (!info->_dynamicMetaObject) { setupDynamicMetaObjectChain(((PythonQtClassWrapper*)Py_TYPE(wrapper)), prototypeMetaObject); } return info->_dynamicMetaObject; } return prototypeMetaObject; } const QMetaObject* PythonQtPrivate::setupDynamicMetaObjectChain(PythonQtClassWrapper* type, const QMetaObject* prototypeMetaObject) { if (!type->_dynamicClassInfo->_dynamicMetaObject) { PyTypeObject* superType = ((PyTypeObject*)type)->tp_base; const QMetaObject* metaObjectOfParent = prototypeMetaObject; if (((PythonQtClassWrapper*)superType)->_dynamicClassInfo) { metaObjectOfParent = setupDynamicMetaObjectChain((PythonQtClassWrapper*)superType, prototypeMetaObject); } return buildDynamicMetaObject(type, metaObjectOfParent); } else { return type->_dynamicClassInfo->_dynamicMetaObject; } } const QMetaObject* PythonQtPrivate::buildDynamicMetaObject(PythonQtClassWrapper* type, const QMetaObject* prototypeMetaObject) { //std::cout << "creating " << ((PyTypeObject*)type)->tp_name << " derived from " << prototypeMetaObject->className() << std::endl; QMetaObjectBuilder builder; builder.setSuperClass(prototypeMetaObject); builder.setClassName(((PyTypeObject*)type)->tp_name); PyObject* dict = ((PyTypeObject*)type)->tp_dict; Py_ssize_t pos = 0; PyObject* value = nullptr; PyObject* key = nullptr; static PyObject* qtSlots = PyUnicode_FromString("_qtSlots"); bool needsMetaObject = false; // Iterate over all members and check if they affect the QMetaObject: // First look for signals: while (PyDict_Next(dict, &pos, &key, &value)) { if (PythonQtSignalFunction_Check(value)) { // A signal object, register with the meta object PythonQtSignalFunctionObject* signal = (PythonQtSignalFunctionObject*)value; if (signal->_dynamicInfo) { signal->_dynamicInfo->name = PyUnicode_AsUTF8(key); for (const QByteArray& sig : qAsConst(signal->_dynamicInfo->signatures)) { builder.addSignal(signal->_dynamicInfo->name + "(" + sig + ")"); needsMetaObject = true; } } } } pos = 0; value = nullptr; key = nullptr; // Now look for slots: (this is a bug in QMetaObjectBuilder, all signals need to be added first) while (PyDict_Next(dict, &pos, &key, &value)) { if (PythonQtProperty_Check(value)) { needsMetaObject = true; PythonQtProperty* prop = (PythonQtProperty*)value; QMetaPropertyBuilder newProp = builder.addProperty(PyUnicode_AsUTF8(key), prop->data->cppType); newProp.setReadable(true); newProp.setWritable(prop->data->fset != nullptr); newProp.setResettable(prop->data->freset != nullptr); newProp.setDesignable(prop->data->designable); newProp.setScriptable(prop->data->scriptable); newProp.setStored(prop->data->stored); newProp.setUser(prop->data->user); newProp.setConstant(prop->data->constant); newProp.setFinal(prop->data->final); if (prop->data->notify) { PythonQtSignalFunctionObject* signal = (PythonQtSignalFunctionObject*)prop->data->notify; if (signal->_dynamicInfo) { QByteArray sig = signal->_dynamicInfo->signatures.at(0); QByteArray fullSig = signal->_dynamicInfo->name + "(" + sig + ")"; int idx = builder.indexOfSignal(fullSig); if (idx != -1) { newProp.setNotifySignal(builder.method(idx)); } else { std::cerr << "could not find notify signal signature " << fullSig.constData(); } } } } if (PyFunction_Check(value) && PyObject_HasAttr(value, qtSlots)) { // A function which has a "_qtSlots" signature list, add the slots to the meta object PyObject* signatures = PyObject_GetAttr(value, qtSlots); Py_ssize_t count = PyList_Size(signatures); for (Py_ssize_t i = 0; i < count; i++) { PyObject* signature = PyList_GET_ITEM(signatures, i); QByteArray sig = PyUnicode_AsUTF8(signature); // Split the return type and the rest of the signature, // no spaces should be in the rest of the signature... QList parts = sig.split(' '); QMetaMethodBuilder method = builder.addSlot(parts[1]); // set the return type of the slot method.setReturnType(parts[0]); needsMetaObject = true; } } // TODO: handle enums, classinfo, ... } if (needsMetaObject) { type->_dynamicClassInfo->_dynamicMetaObject = builder.toMetaObject(); type->_dynamicClassInfo->_classInfo = new PythonQtClassInfo(); type->_dynamicClassInfo->_classInfo->setupQObject(type->_dynamicClassInfo->_dynamicMetaObject); } else { // we don't need an own meta object, just use the one from our base class type->_dynamicClassInfo->_dynamicMetaObject = prototypeMetaObject; } return type->_dynamicClassInfo->_dynamicMetaObject; } int PythonQtPrivate::handleMetaCall(QObject* object, PythonQtInstanceWrapper* wrapper, QMetaObject::Call call, int id, void** args) { const QMetaObject* meta = object->metaObject(); int methodCount = meta->methodCount(); if (call == QMetaObject::InvokeMetaMethod) { QMetaMethod method = meta->method(id); if (method.methodType() == QMetaMethod::Signal) { // just emit the signal, there is no Python code QMetaObject::activate(object, id, args); } else { PYTHONQT_GIL_SCOPE callMethodInPython(method, wrapper, args); } } else { PYTHONQT_GIL_SCOPE QMetaProperty metaProp = meta->property(id); if (!metaProp.isValid()) { return id - methodCount; } PythonQtProperty* prop = nullptr; // Get directly from the Python class, since we don't want to get the value of the property PyObject* maybeProp = PyBaseObject_Type.tp_getattro((PyObject*)wrapper, PyUnicode_FromString(metaProp.name())); if (maybeProp && PythonQtProperty_Check(maybeProp)) { prop = (PythonQtProperty*)maybeProp; } else { return id - methodCount; } const PythonQtMethodInfo::ParameterInfo& info = PythonQtMethodInfo::getParameterInfoForMetaType(metaProp.userType()); if (call == QMetaObject::WriteProperty) { PyObject* value = PythonQtConv::ConvertQtValueToPython(info, args[0]); bool ok = prop->data->callSetter((PyObject*)wrapper, value); Py_XDECREF(value); return ok ? 0 : -1; } else if (call == QMetaObject::ReadProperty) { PyObject* value = prop->data->callGetter((PyObject*)wrapper); if (value) { void* result = PythonQtConv::ConvertPythonToQt(info, value, false, nullptr, args[0]); Py_DECREF(value); return (result == nullptr ? -1 : 0); } else { return -1; } } else if (call == QMetaObject::ResetProperty) { bool ok = prop->data->callReset((PyObject*)wrapper); return ok ? 0 : -1; } } return id - methodCount; } void PythonQtPrivate::callMethodInPython(QMetaMethod& method, PythonQtInstanceWrapper* wrapper, void** args) { PyObject* func = PyObject_GetAttrString((PyObject*)wrapper, method.name()); if (func) { const PythonQtMethodInfo* methodInfo = PythonQtMethodInfo::getCachedMethodInfo(method, nullptr); PyObject* result = PythonQtSignalTarget::call(func, methodInfo, args, false); if (result) { PythonQtConv::ConvertPythonToQt(methodInfo->parameters().at(0), result, false, nullptr, args[0]); // TODO: handle error? //PythonQt::priv()->handleVirtualOverloadReturnError("devType", methodInfo, result); } Py_XDECREF(result); Py_DECREF(func); } } QString PythonQtPrivate::getSignature(PyObject* object) { QString signature; if (object) { PyMethodObject* method = nullptr; PyFunctionObject* func = nullptr; bool decrefMethod = false; if (PythonQtUtils::isPythonClassType(object)) { method = (PyMethodObject*)PyObject_GetAttrString(object, "__init__"); decrefMethod = true; } else if (object->ob_type == &PyFunction_Type) { func = (PyFunctionObject*)object; } else if (object->ob_type == &PyMethod_Type) { method = (PyMethodObject*)object; } if (method) { if (PyFunction_Check(method->im_func)) { func = (PyFunctionObject*)method->im_func; } else if (isMethodDescriptor((PyObject*)method)) { QString docstr; PyObject* doc = PyObject_GetAttrString(object, "__doc__"); if (doc) { docstr = PyUnicode_AsUTF8(doc); Py_DECREF(doc); } PyObject* s = PyObject_GetAttrString(object, "__name__"); if (s) { Q_ASSERT(PyUnicode_Check(s)); signature = PyUnicode_AsUTF8(s); if (docstr.startsWith(signature + "(")) { signature = docstr; } else { signature += "(...)"; if (!docstr.isEmpty()) { signature += "\n\n" + docstr; } } Py_DECREF(s); } } } if (func) { QString funcName; PyObject* s = PyObject_GetAttrString((PyObject*)func, "__name__"); if (s) { Q_ASSERT(PyUnicode_Check(s)); funcName = PyUnicode_AsUTF8(s); Py_DECREF(s); } if (method && funcName == "__init__") { PyObject* s = PyObject_GetAttrString(object, "__name__"); if (s) { Q_ASSERT(PyUnicode_Check(s)); funcName = PyUnicode_AsUTF8(s); Py_DECREF(s); } } QStringList arguments; QStringList defaults; QString varargs; QString varkeywords; // NOTE: This implementation is based on function getargs() in inspect.py. // inspect.getargs() can handle anonymous (tuple) arguments, while this code does not. // It can be implemented, but it may be rarely needed and not necessary. PyCodeObject* code = (PyCodeObject*)func->func_code; if (auto co_varnames = PyCode_GetVarnames(code)) { int nargs = code->co_argcount; Q_ASSERT(PyTuple_Check(co_varnames)); for (int i = 0; i < nargs; i++) { PyObject* name = PyTuple_GetItem(co_varnames, i); Q_ASSERT(PyUnicode_Check(name)); arguments << PyUnicode_AsUTF8(name); } if (code->co_flags & CO_VARARGS) { PyObject* s = PyTuple_GetItem(co_varnames, nargs); Q_ASSERT(PyUnicode_Check(s)); varargs = PyUnicode_AsUTF8(s); nargs += 1; } if (code->co_flags & CO_VARKEYWORDS) { PyObject* s = PyTuple_GetItem(co_varnames, nargs); Q_ASSERT(PyUnicode_Check(s)); varkeywords = PyUnicode_AsUTF8(s); } Py_DECREF(co_varnames); } PyObject* defaultsTuple = func->func_defaults; if (defaultsTuple) { Q_ASSERT(PyTuple_Check(defaultsTuple)); for (Py_ssize_t i = 0; i < PyTuple_Size(defaultsTuple); i++) { PyObject* d = PyTuple_GetItem(defaultsTuple, i); PyObject* s = PyObject_Repr(d); Q_ASSERT(PyUnicode_Check(s)); defaults << PyUnicode_AsUTF8(s); Py_DECREF(s); } } int firstdefault = arguments.size() - defaults.size(); for (int i = 0; i < arguments.size(); i++) { if (!signature.isEmpty()) { signature += ", "; } if (!method || i > 0 || arguments[i] != "self") { signature += arguments[i]; if (i >= firstdefault) { signature += "=" + defaults[i - firstdefault]; } } } if (!varargs.isEmpty()) { if (!signature.isEmpty()) { signature += ", "; } signature += "*" + varargs; } if (!varkeywords.isEmpty()) { if (!signature.isEmpty()) { signature += ", "; } signature += "**" + varkeywords; } signature = funcName + "(" + signature + ")"; } if (method && decrefMethod) { Py_DECREF(method); } } return signature; } void PythonQtPrivate::shellClassDeleted(void* shellClass) { PYTHONQT_GIL_SCOPE PythonQtInstanceWrapper* wrap = _wrappedObjects.value(shellClass); if (wrap) { if (wrap->_wrappedPtr) { // this is a pure C++ wrapper and the shell has gone, so we need // to set the _wrappedPtr to NULL on the wrapper wrap->_wrappedPtr = nullptr; // and then we remove the wrapper, since the wrapped class is gone _wrappedObjects.remove(shellClass); } // if the wrapper is a QObject, we do not handle this here, // it will be handled by the QPointer<> to the QObject, which becomes NULL // via the QObject destructor. // if the shell was owned by C++ and is deleted via C++, we // need to decrement the ref-count of the wrapper so that is can // be released. if (wrap->_shellInstanceRefCountsWrapper) { Py_DECREF((PyObject*)wrap); wrap->_shellInstanceRefCountsWrapper = false; } } } PyObject* PythonQtPrivate::wrapMemoryAsBuffer(const void* data, Py_ssize_t size) { return PyMemoryView_FromMemory((char*)data, size, PyBUF_READ); } PyObject* PythonQtPrivate::wrapMemoryAsBuffer(void* data, Py_ssize_t size) { return PyMemoryView_FromMemory((char*)data, size, PyBUF_WRITE); } PythonQtClassInfo* PythonQtPrivate::getClassInfo(const QMetaObject* meta) { return getClassInfo(QByteArray(meta->className())); } PythonQtClassInfo* PythonQtPrivate::getClassInfo(const QByteArray& className) { PythonQtClassInfo* result = _knownClassInfos.value(className); if (!result) { static bool recursion = false; if (!recursion) { if (_knownLazyClasses.contains(className)) { QByteArray module = _knownLazyClasses.value(className); recursion = true; PyImport_ImportModule(module); recursion = false; result = _knownClassInfos.value(className); if (!result) { std::cerr << "PythonQt lazy import " << module.constData() << " did not resolve " << className.constData() << std::endl; } } } } return result; } void PythonQtPrivate::registerLazyClass(const QByteArray& name, const QByteArray& moduleToImport) { _knownLazyClasses.insert(name, moduleToImport); }