/* indent-tabs-mode: nil -*- */
/*
qore Python module
Copyright (C) 2020 - 2022 Qore Technologies, s.r.o.
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.
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "PythonQoreClass.h"
#include "QoreLoader.h"
#include "QorePythonProgram.h"
#include "QorePythonStackLocationHelper.h"
#include
#include
#include
#include
#include
static constexpr const char* QCLASS_KEY = "__$QCLS__";
static int qore_exception_init(PyObject* self, PyObject* args, PyObject* kwds) {
//QorePythonReferenceHolder argstr(PyObject_Repr(args));
//printd(5, "qore_exception_init() self: %p args: %s\n", self, PyUnicode_AsUTF8(*argstr));
assert(PyTuple_Check(args));
Py_ssize_t size = PyTuple_Size(args);
if (!size) {
return -1;
}
PyObject* err = PyTuple_GetItem(args, 0);
if (!PyUnicode_Check(err)) {
QorePythonReferenceHolder err_repr(PyObject_Repr(err));
if (PyObject_SetAttrString(self, "err", *err_repr) < 0) {
return -1;
}
} else {
if (PyObject_SetAttrString(self, "err", err) < 0) {
return -1;
}
}
if (size > 1) {
PyObject* desc = PyTuple_GetItem(args, 1);
if (!PyUnicode_Check(desc)) {
QorePythonReferenceHolder desc_repr(PyObject_Repr(desc));
if (PyObject_SetAttrString(self, "desc", *desc_repr) < 0) {
return -1;
}
} else {
if (PyObject_SetAttrString(self, "desc", desc) < 0) {
return -1;
}
}
if (size > 2) {
if (PyObject_SetAttrString(self, "arg", PyTuple_GetItem(args, 2)) < 0) {
return -1;
}
}
}
return 0;
}
PyTypeObject PythonQoreException_Type = {
PyVarObject_HEAD_INIT(nullptr, 0)
#if !defined(__clang__) && __GNUC__ < 8
// g++ 5.4.0 does not accept the short-form initialization below :(
"QoreException", // tp_name
sizeof(PyBaseExceptionObject), // tp_basicsize
0, // tp_itemsize
nullptr, // tp_dealloc
0, // tp_vectorcall_offset/
0, // tp_getattr
0, // tp_setattr
0, // tp_as_async
nullptr, // tp_repr
0, // tp_as_number
0, // tp_as_sequence
0, // tp_as_mapping
0, // tp_hash
0, // tp_call
0, // tp_str
0, // tp_getattro
0, // tp_setattro
0, // tp_as_buffer
Py_TPFLAGS_DEFAULT, // tp_flags
"Qore exception class", // tp_doc
0, // tp_traverse
0, // tp_clear
0, // tp_richcompare
0, // tp_weaklistoffset
0, // tp_iter
0, // tp_iternext
0, // tp_methods
0, // tp_members
0, // tp_getset
reinterpret_cast(PyExc_Exception), // tp_base
0, // tp_dict
0, // tp_descr_get
0, // tp_descr_set
0, // tp_dictoffset
qore_exception_init, // tp_init
0, // tp_alloc
0, // tp_new
#else
.tp_name = "QoreException",
.tp_basicsize = sizeof(PyBaseExceptionObject),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "Qore exception class",
.tp_base = reinterpret_cast(PyExc_Exception),
.tp_init = qore_exception_init,
#endif
};
void PythonQoreClass::py_free(PyQoreObject* self) {
//printd(5, "PythonQoreClass::py_free() self: %p '%s'\n", self, Py_TYPE(self)->tp_name);
PyObject_Del(self);
}
bool PyQoreObject_Check(PyObject* obj) {
return obj && PyObject_TypeCheck(obj, &PythonQoreObjectBase_Type);
}
bool PyQoreObjectType_Check(PyTypeObject* type) {
assert(type);
// FIXME: use PyDict_Contains() instead
return type->tp_dict && PyDict_GetItemString(type->tp_dict, QCLASS_KEY);
}
PythonQoreClass::PythonQoreClass(QorePythonProgram* pypgm, PyTypeObject* type, const QoreClass& qcls) {
Py_INCREF((PyObject*)type);
py_type = type;
pypgm->insertClass(&qcls, this);
// do not save the qore class to the python class, as the python class may be a builtin class and the Qore class
// can be deleted afterwards
}
PythonQoreClass::PythonQoreClass(QorePythonProgram* pypgm, const char* module_name, const QoreClass& qcls, py_cls_map_t::iterator i) {
//printd(5, "PythonQoreClass::PythonQoreClass() %s.%s py_type: %p\n", module_name, qcls.getName(), &py_type);
name.sprintf("%s.%s", module_name, qcls.getName());
const char* namestr = pypgm->saveString(name.c_str());
doc.sprintf("Python wrapper class for Qore class %s", qcls.getName());
const char* docstr = pypgm->saveString(doc.c_str());
PyType_Slot slots[] = {
{Py_tp_doc, (void*)docstr},
{Py_tp_dealloc, (void*)PythonQoreClass::py_dealloc},
{Py_tp_repr, (void*)PythonQoreClass::py_repr},
{Py_tp_getattro, (void*)PythonQoreClass::py_getattro},
{Py_tp_base, (void*)&PythonQoreObjectBase_Type},
{Py_tp_alloc, (void*)PyType_GenericAlloc},
{Py_tp_init, (void*)PythonQoreClass::py_init},
{Py_tp_new, (void*)PythonQoreClass::py_new},
{Py_tp_free, (void*)PythonQoreClass::py_free},
{0, nullptr},
};
PyType_Spec spec = {
.name = namestr,
.basicsize = sizeof(PyQoreObject),
.itemsize = 0,
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.slots = slots,
};
clsset_t cls_set;
// get single base class - Python and Qore's multiple inheritance models are not compatible
// we can only set a single class for the Python base class, so if there are multiple
// base classes, then we populate methods instead directly
QorePythonReferenceHolder bases;
{
PythonQoreClass* base_cls = nullptr;
QoreParentClassIterator ci(qcls);
while (ci.next()) {
if (ci.getAccess() > Private) {
continue;
}
base_cls = pypgm->findCreatePythonClass(ci.getParentClass(), module_name);
cls_set.insert(&ci.getParentClass());
bases = PyTuple_New(1);
PyObject* py_base_cls = reinterpret_cast(base_cls->getPythonType());
Py_INCREF(py_base_cls);
PyTuple_SET_ITEM(*bases, 0, py_base_cls);
break;
}
}
py_type = bases
? reinterpret_cast(PyType_FromSpecWithBases(&spec, *bases))
: reinterpret_cast(PyType_FromSpec(&spec));
//printd(5, "PythonQoreClass::PythonQoreClass() %s py_type: %p\n", qcls.getName(), py_type);
assert(py_type);
assert(py_type->tp_dict);
// set of normal method names
cstrset_t meth_set;
pypgm->insertClass(i, &qcls, this);
// setup class
populateClass(pypgm, qcls, cls_set, meth_set);
// add normal methods
for (size_t i = 0; i < py_normal_meth_vec.size(); ++i) {
PyMethodDef& md = py_normal_meth_vec[i];
QorePythonReferenceHolder method_capsule(py_normal_meth_obj_vec[i].release());
QorePythonReferenceHolder func(PyCFunction_New(&md, *method_capsule));
QorePythonReferenceHolder meth(PyInstanceMethod_New(*func));
assert(meth);
const QoreMethod* m = reinterpret_cast(PyCapsule_GetPointer(*method_capsule, nullptr));
PyDict_SetItemString(py_type->tp_dict, m->getName(), *meth);
}
py_normal_meth_obj_vec.clear();
// add static methods
for (size_t i = 0; i < py_static_meth_vec.size(); ++i) {
PyMethodDef& md = py_static_meth_vec[i];
QorePythonReferenceHolder method_capsule(py_static_meth_obj_vec[i].release());
QorePythonReferenceHolder func(PyCFunction_New(&md, *method_capsule));
QorePythonReferenceHolder meth(PyStaticMethod_New(*func));
assert(meth);
const QoreMethod* m = reinterpret_cast(PyCapsule_GetPointer(*method_capsule, nullptr));
PyDict_SetItemString(py_type->tp_dict, m->getName(), *meth);
}
py_static_meth_obj_vec.clear();
// add Qore class to type dictionary
QorePythonReferenceHolder qore_class(PyCapsule_New((void*)&qcls, nullptr, nullptr));
PyDict_SetItemString(py_type->tp_dict, QCLASS_KEY, *qore_class);
}
PythonQoreClass::~PythonQoreClass() {
printd(5, "PythonQoreClass::~PythonQoreClass() this: %p '%s'\n", this, name.c_str());
// py_type should have been released via release() before interpreter deletion
// Only decref if it's still set (shouldn't happen in normal cleanup)
if (py_type) {
Py_DECREF(py_type);
}
}
void PythonQoreClass::release() {
// Release Python reference before interpreter is cleared
// This must be called while the interpreter is still valid
if (py_type) {
Py_DECREF(py_type);
py_type = nullptr;
}
}
void PythonQoreClass::clearMethods() {
// CRITICAL: Clear method objects from the type dictionary.
// Method objects created with PyCFunction_New reference PyMethodDef structures stored
// in py_normal_meth_vec and py_static_meth_vec. If the type survives (e.g., due to
// references from the main interpreter), these method objects will be traversed during
// GC after the PyMethodDef structures are freed, causing use-after-free crashes.
// By clearing the dict, we ensure method objects are decreffed while the interpreter
// is still valid.
if (py_type && py_type->tp_dict) {
// Remove all method objects we added
for (const auto& md : py_normal_meth_vec) {
if (md.ml_name) {
PyDict_DelItemString(py_type->tp_dict, md.ml_name);
PyErr_Clear(); // Ignore errors if key doesn't exist
}
}
for (const auto& md : py_static_meth_vec) {
if (md.ml_name) {
PyDict_DelItemString(py_type->tp_dict, md.ml_name);
PyErr_Clear(); // Ignore errors if key doesn't exist
}
}
// Also remove the Qore class capsule
PyDict_DelItemString(py_type->tp_dict, QCLASS_KEY);
PyErr_Clear();
}
}
void PythonQoreClass::populateClass(QorePythonProgram* pypgm, const QoreClass& qcls, clsset_t& cls_set,
cstrset_t& meth_set, bool skip_first) {
//printd(5, "PythonQoreClass::populateClass() cls: %s cs: %d ms: %d\n", qcls.getName(), (int)cls_set.size(),
// (int)meth_set.size());
{
QoreMethodIterator i(qcls);
while (i.next()) {
const QoreMethod* m = i.getMethod();
if (m->getAccess() > Private) {
continue;
}
//printd(5, "PythonQoreClass::populateClass() adding %s -> %s::%s()\n", name.c_str(), qcls.getName(),
// m->getName());
cstrset_t::iterator mi = meth_set.lower_bound(m->getName());
if (mi == meth_set.end() || strcmp(*mi, m->getName())) {
meth_set.insert(mi, m->getName());
QoreStringMaker mdoc("Python wrapper for Qore class method %s::%s()", qcls.getName(), m->getName());
const char* mdocstr = pypgm->saveString(mdoc.c_str());
py_normal_meth_vec.push_back({m->getName(), (PyCFunction)exec_qore_method, METH_VARARGS, mdocstr});
py_normal_meth_obj_vec.push_back(PyCapsule_New((void*)m, nullptr, nullptr));
}
}
}
{
QoreStaticMethodIterator i(qcls);
while (i.next()) {
const QoreMethod* m = i.getMethod();
if (m->getAccess() > Private) {
continue;
}
// issue #4397: check if there is an accessible normal method with the same name, if so, skip it
{
ClassAccess access;
const QoreMethod* nm = qcls.findMethod(m->getName(), access);
if (nm && access < Internal) {
continue;
}
}
//printd(5, "PythonQoreClass::populateClass() adding %s -> static %s::%s()\n", name.c_str(),
// qcls.getName(), m->getName());
cstrset_t::iterator mi = meth_set.lower_bound(m->getName());
if (mi == meth_set.end() || strcmp(*mi, m->getName())) {
meth_set.insert(mi, m->getName());
QoreStringMaker mdoc("Python wrapper for Qore static class method %s::%s()", qcls.getName(),
m->getName());
const char* mdocstr = pypgm->saveString(mdoc.c_str());
py_static_meth_vec.push_back({m->getName(), (PyCFunction)exec_qore_static_method, METH_VARARGS,
mdocstr});
py_static_meth_obj_vec.push_back(PyCapsule_New((void*)m, nullptr, nullptr));
}
}
}
ExceptionSink xsink;
{
QoreClassConstantIterator i(qcls);
while (i.next()) {
const QoreExternalConstant& c = i.get();
if (c.getAccess() > Private) {
continue;
}
cstrset_t::iterator mi = meth_set.lower_bound(c.getName());
if (mi == meth_set.end() || strcmp(*mi, c.getName())) {
meth_set.insert(mi, c.getName());
ValueHolder qoreval(c.getReferencedValue(), &xsink);
QorePythonProgram* qore_python_pgm = QorePythonProgram::getContext();
QorePythonReferenceHolder val(qore_python_pgm->getPythonValue(*qoreval, &xsink));
if (!xsink) {
assert(val);
PyDict_SetItemString(py_type->tp_dict, c.getName(), *val);
}
}
}
}
bool first = false;
QoreParentClassIterator ci(qcls);
while (ci.next()) {
if (ci.getAccess() > Private) {
continue;
}
// skip the first class, as it's already been added as a base class
if (skip_first && !first) {
first = true;
continue;
}
const QoreClass& parent_cls = ci.getParentClass();
clsset_t::iterator i = cls_set.lower_bound(&parent_cls);
if (i != cls_set.end() && ((*i) == &parent_cls)) {
//printd(5, "PythonQoreClass::populateClass() %s skipping parent %s\n", qcls.getName(), parent_cls.getName());
continue;
}
//printd(5, "PythonQoreClass::populateClass() %s parent <- %s\n", qcls.getName(), parent_cls.getName());
cls_set.insert(i, &parent_cls);
populateClass(pypgm, parent_cls, cls_set, meth_set, false);
}
}
PyObject* PythonQoreClass::wrap(QoreObject* obj) {
PyQoreObject* self = (PyQoreObject*)py_type->tp_alloc(py_type, 0);
obj->tRef();
self->qobj = obj;
// save a strong reference to the Qore object
ExceptionSink xsink;
QorePythonProgram* qore_python_pgm = QorePythonProgram::getContext();
qore_python_pgm->saveQoreObjectFromPython(obj, xsink);
if (xsink) {
qore_python_pgm->raisePythonException(xsink);
}
//printd(5, "PythonQoreClass::wrap() obj: %p (%s)\n", obj, obj->getClassName());
return (PyObject*)self;
}
PyObject* PythonQoreClass::exec_qore_method(PyObject* method_capsule, PyObject* args) {
// Save the thread state Python had when calling us - must restore before returning
PyThreadState* entry_tstate = _qore_safe_thread_state_get();
QoreForeignThreadHelper qfth;
// get method
const QoreMethod* m = reinterpret_cast(PyCapsule_GetPointer(method_capsule, nullptr));
assert(PyTuple_Check(args));
QoreObject* obj;
// check if this could be a static method call
if (!PyTuple_Size(args)) {
obj = nullptr;
} else {
// returns a borrowed reference
PyObject* py_obj = PyTuple_GetItem(args, 0);
if (!PyQoreObject_Check(py_obj)) {
obj = nullptr;
} else {
obj = reinterpret_cast(py_obj)->qobj;
// if the class is not accessible, do not make the call
if (obj && obj->getClassAccess(*m->getClass()) > Private) {
obj = nullptr;
}
}
}
#if 0
{
QorePythonReferenceHolder argstr(PyObject_Repr(args));
printd(5, "PythonQoreClass::exec_qore_method() %s::%s() obj: %p (%s) args: %s\n", m->getClassName(),
m->getName(), obj, obj ? obj->getClassName() : "n/a", PyUnicode_AsUTF8(*argstr));
}
#endif
if (!obj) {
// see if a static method with the same name is available
ClassAccess access;
const QoreMethod* static_meth = m->getClass()->findStaticMethod(m->getName(), access);
if (!static_meth || access > Private) {
QoreStringMaker desc("cannot call normal method '%s::%s()' without a 'self' object argument that " \
"inherits '%s'", m->getClassName(), m->getName(), m->getClassName());
PyErr_SetString(PyExc_ValueError, desc.c_str());
return nullptr;
}
//printd(5, "about to call exec_qore_static_method()\n");
return exec_qore_static_method(*static_meth, args, 0);
}
ExceptionSink xsink;
QorePythonProgram* qore_python_pgm = QorePythonProgram::getContext();
{
// issue #4330: get Qore program from method if possible
QoreProgram* pgm = m->getClass()->getProgram();
if (!pgm) {
pgm = qore_python_pgm->getQoreProgram();
}
QorePythonHelper qph(qore_python_pgm, &xsink);
if (!xsink) {
QoreExternalProgramContextHelper pch(&xsink, pgm);
if (!xsink) {
ReferenceHolder qargs(qore_python_pgm->getQoreListFromTuple(&xsink, args, 1), &xsink);
if (!xsink) {
ValueHolder rv(&xsink);
{
QorePythonReleaseGilHelper prgh;
QorePythonStackLocationHelper slh(qph.getOldProgram());
rv = obj->evalMethod(*m, *qargs, &xsink);
}
if (!xsink) {
QorePythonReferenceHolder py_rv(qore_python_pgm->getPythonValue(*rv, &xsink));
if (!xsink) {
assert(py_rv);
return py_rv.release();
}
}
}
}
}
}
// issue #4329: exception must be thrown in the calling context
// IMPORTANT: Restore the entry thread state before setting the exception.
// QorePythonHelper may have changed thread states, but Python expects the
// exception to be on the thread state it passed when calling us.
PyThreadState* exit_tstate = _qore_safe_thread_state_get();
if (exit_tstate != entry_tstate) {
PyThreadState_Swap(entry_tstate);
}
if (xsink) {
qore_python_pgm->raisePythonException(xsink);
return nullptr;
}
// Fallback - should not reach here normally
return nullptr;
}
PyObject* PythonQoreClass::exec_qore_static_method(PyObject* method_capsule, PyObject* args) {
printd(5, "exec_qore_static_method() args: %p\n", args);
QoreForeignThreadHelper qfth;
// get method
const QoreMethod* m = reinterpret_cast(PyCapsule_GetPointer(method_capsule, nullptr));
assert(PyTuple_Check(args));
#if 0
{
QorePythonReferenceHolder argstr(PyObject_Repr(args));
assert(PyUnicode_Check(*argstr));
printd(1, "PythonQoreClass::exec_qore_static_method() %s::%s() args: %s\n", m->getClassName(), m->getName(),
PyUnicode_AsUTF8(*argstr));
}
#endif
return exec_qore_static_method(*m, args);
}
PyObject* PythonQoreClass::exec_qore_static_method(const QoreMethod& m, PyObject* args, size_t offset) {
// Save the thread state Python had when calling us - must restore before returning
PyThreadState* entry_tstate = _qore_safe_thread_state_get();
ExceptionSink xsink;
QorePythonProgram* qore_python_pgm = QorePythonProgram::getContext();
{
// issue #4330: get Qore program from method if possible
QoreProgram* pgm = m.getClass()->getProgram();
if (!pgm) {
pgm = qore_python_pgm->getQoreProgram();
}
QorePythonHelper qph(qore_python_pgm, &xsink);
if (!xsink) {
QoreExternalProgramContextHelper pch(&xsink, pgm);
if (!xsink) {
ReferenceHolder qargs(qore_python_pgm->getQoreListFromTuple(&xsink, args, offset), &xsink);
if (!xsink) {
ValueHolder rv(&xsink);
{
QorePythonReleaseGilHelper prgh;
QorePythonStackLocationHelper slh(qph.getOldProgram());
rv = QoreObject::evalStaticMethod(m, m.getClass(), *qargs, &xsink);
}
if (!xsink) {
QorePythonReferenceHolder py_rv(qore_python_pgm->getPythonValue(*rv, &xsink));
if (!xsink) {
assert(py_rv);
return py_rv.release();
}
}
}
}
}
}
// issue #4329: exception must be thrown in the calling context
// IMPORTANT: Restore the entry thread state before setting the exception.
// QorePythonHelper may have changed thread states, but Python expects the
// exception to be on the thread state it passed when calling us.
PyThreadState* exit_tstate = _qore_safe_thread_state_get();
if (exit_tstate != entry_tstate) {
PyThreadState_Swap(entry_tstate);
}
if (xsink) {
qore_python_pgm->raisePythonException(xsink);
return nullptr;
}
// Fallback - should not reach here normally
return nullptr;
}
int PythonQoreClass::py_init(PyObject* self, PyObject* args, PyObject* kwds) {
assert(PyQoreObject_Check(self));
assert(PyTuple_Check(args));
//QorePythonReferenceHolder argstr(PyObject_Repr(args));
//printd(5, "PythonQoreClass::py_init() self: %p '%s' args: %p (%d: %s) kwds: %p\n", self, Py_TYPE(self)->tp_name,
// args, (int)PyTuple_Size(args), PyUnicode_AsUTF8(*argstr), kwds);
QorePythonProgram* qore_python_pgm = QorePythonProgram::getExecutionContext();
ExceptionSink xsink;
QoreExternalProgramContextHelper pch(&xsink, qore_python_pgm->getQoreProgram());
if (xsink) {
qore_python_pgm->raisePythonException(xsink);
return -1;
}
// the current Qore class
const QoreClass* qcls;
// the Qore class to be constructed
const QoreClass* constructor_cls;
PyTypeObject* type = Py_TYPE(self);
if (!PyQoreObjectType_Check(type)) {
constructor_cls = findQoreClass(self);
assert(type->tp_base);
// create Qore type for Python type
qcls = qore_python_pgm->getCreateQorePythonClass(&xsink, type);
printd(5, "PythonQoreClass::py_init() self: %p type: %s got context pypgm: %p\n", self, type->tp_name,
qore_python_pgm);
} else {
constructor_cls = qcls = getQoreClass(type);
}
PyQoreObject* pyself = reinterpret_cast(self);
// returns a borrowed reference
QoreObject* qobj = QorePythonImplicitQoreArgHelper::getQoreObject();
//printd(5, "PythonQoreClass::py_init() self: %p py_cls: '%s' qcls: '%s' (%d) cq: '%s' (%d) qobj: %p args: %p\n",
// self, type->tp_name, qcls->getName(), qcls->getID(), constructor_cls->getName(), constructor_cls->getID(),
// qobj, args);
if (qobj && qobj->getClass() == qcls) {
qobj->tRef();
pyself->qobj = qobj;
return 0;
}
// issue #4044: do not allow abstract classes to be constructed
if (!qcls->runtimeCheckInstantiateClass(&xsink)) {
ReferenceHolder qargs(qore_python_pgm->getQoreListFromTuple(&xsink, args, 0, true), &xsink);
if (!xsink) {
QoreExternalProgramContextHelper pch(&xsink, qore_python_pgm->getQoreProgram());
if (!xsink) {
QoreObject* created_obj = nullptr;
{
QorePythonReleaseGilHelper prgh;
QorePythonStackLocationHelper slh(qore_python_pgm);
ReferenceHolder qobj(constructor_cls->execConstructor(*qcls, *qargs, true, &xsink),
&xsink);
if (!xsink) {
printd(5, "PythonQoreClass::py_init() self: %p created Qore %s object (args: %p %d): %p (%s)\n",
self, qcls->getName(), *qargs, qargs ? (int)qargs->size() : 0, *qobj, qobj->getClassName());
created_obj = qobj.release();
}
}
// GIL is now held again, safe to call newQoreObject which calls Py_INCREF
if (created_obj) {
return newQoreObject(xsink, pyself, created_obj, qcls == constructor_cls ? nullptr : qcls,
qore_python_pgm);
}
}
}
}
qore_python_pgm->raisePythonException(xsink);
assert(PyErr_Occurred());
return -1;
}
int PythonQoreClass::newQoreObject(ExceptionSink& xsink, PyQoreObject* pyself, QoreObject* qobj,
const QoreClass* qcls, QorePythonProgram* qore_python_pgm) {
qobj->tRef();
pyself->qobj = qobj;
if (qcls) {
// add private data for python class
Py_INCREF(pyself);
qobj->setPrivate(qcls->getID(), new QorePythonPrivateData((PyObject*)pyself));
}
// save a strong reference to the Qore object
qore_python_pgm->saveQoreObjectFromPython(qobj, xsink);
if (!xsink) {
return 0;
}
qore_python_pgm->raisePythonException(xsink);
assert(PyErr_Occurred());
return -1;
}
PyObject* PythonQoreClass::py_getattro(PyObject* self, PyObject* attr) {
// first try to get python attribute
PyObject* pyrv = PyObject_GenericGetAttr(self, attr);
if (pyrv) {
return pyrv;
}
// if that failed, then clear the error and try to get a Qore member
PyErr_Clear();
assert(PyQoreObject_Check(self));
assert(PyUnicode_Check(attr));
const QoreClass* qcls = findQoreClass(self);
if (!qcls) {
return nullptr;
}
const char* member = PyUnicode_AsUTF8(attr);
ExceptionSink xsink;
QoreObject* obj = reinterpret_cast(self)->qobj;
if (!obj) {
Py_INCREF(Py_None);
return Py_None;
}
//printd(5, "PythonQoreClass::py_getattro() obj %p %s.%s\n", obj, qcls->getName(), member);
QorePythonProgram* qore_python_pgm = QorePythonProgram::getExecutionContext();
QoreExternalProgramContextHelper pch(&xsink, qore_python_pgm->getQoreProgram());
if (!xsink) {
ValueHolder v(&xsink);
{
QorePythonReleaseGilHelper prgh;
QorePythonStackLocationHelper slh(qore_python_pgm);
v = obj->evalMember(member, &xsink);
}
printd(5, "PythonQoreClass::py_getattro() obj %p %s.%s = %s\n", obj, qcls->getName(), member, v->getFullTypeName());
if (!xsink) {
QorePythonReferenceHolder rv(qore_python_pgm->getPythonValue(*v, &xsink));
if (!xsink) {
return rv.release();
}
}
}
qore_python_pgm->raisePythonException(xsink);
assert(PyErr_Occurred());
return nullptr;
}
const QoreClass* PythonQoreClass::findQoreClass(PyObject* self) {
PyTypeObject* type = Py_TYPE(self);
// get base Qore class
while (!PyQoreObjectType_Check(type)) {
if (!type->tp_base) {
ExceptionSink xsink;
xsink.raiseException("QORE-ERROR", "cannot initialize Python class '%s' as a derived class of a Qore " \
"base class; no Qore base class found", Py_TYPE(self)->tp_name);
qore_python_pgm->raisePythonException(xsink);
return nullptr;
}
type = type->tp_base;
}
return getQoreClass(type);
}
const QoreClass* PythonQoreClass::getQoreClass(PyTypeObject* type) {
assert(type->tp_dict);
PyObject* obj = PyDict_GetItemString(type->tp_dict, QCLASS_KEY);
assert(obj);
assert(PyCapsule_CheckExact(obj));
const QoreClass* qcls = reinterpret_cast(PyCapsule_GetPointer(obj, nullptr));
assert(qcls);
return qcls;
}
PyObject* PythonQoreClass::py_new(PyTypeObject* type, PyObject* args, PyObject* kw) {
return type->tp_alloc(type, 0);
}
void PythonQoreClass::py_dealloc(PyQoreObject* self) {
if (self->qobj) {
self->qobj->tDeref();
self->qobj = nullptr;
}
Py_TYPE(self)->tp_free(self);
}
PyObject* PythonQoreClass::py_repr(PyObject* obj) {
QoreStringMaker str("Qore %s object %p", Py_TYPE(obj)->tp_name, obj);
return PyUnicode_FromStringAndSize(str.c_str(), str.size());
}