See More

/* -*- mode: c++; indent-tabs-mode: nil -*- */ /** @file QorePythonProgram.cpp defines the QorePythonProgram class */ /* QorePythonProgram.qpp Qore Programming Language Copyright 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 "QC_PythonProgram.h" #include "QorePythonPrivateData.h" #include "QorePythonClass.h" #include "QoreLoader.h" #include "PythonQoreClass.h" #include "QoreMetaPathFinder.h" #include "PythonCallableCallReferenceNode.h" #include "PythonQoreCallable.h" #include "ModuleNamespace.h" #include "QorePythonStackLocationHelper.h" #include #include #include #include #include static const char* this_file = q_basenameptr(__FILE__); // type used for imported Qore namespaces in Python; a plan type with only a dictionary struct PythonQoreNs { PyObject_HEAD PyObject* dict; }; static strvec_t get_dot_path_list(const std::string str) { strvec_t rv; size_t start = 0; while (true) { size_t pos = str.find('.', start); rv.push_back(str.substr(start, pos - start)); if (pos == std::string::npos) { break; } start = pos + 1; } return rv; } #ifdef DEBUG // from Python internal code static bool _qore_PyThreadState_IsCurrent(PyThreadState* tstate) { // Must be the tstate for this thread return tstate == _qore_PyRuntimeGILState_GetThreadState(); } #endif // static member declarations QorePythonProgram::py_thr_map_t QorePythonProgram::py_thr_map; QorePythonProgram::py_global_tid_map_t QorePythonProgram::py_global_tid_map; QoreThreadLock QorePythonProgram::py_thr_lck; unsigned QorePythonProgram::pgm_count = 0; QorePythonProgram::QorePythonProgram() : save_object_callback(nullptr) { printd(5, "QorePythonProgram::QorePythonProgram() this: %p\n", this); assert(PyGILState_Check()); PyThreadState* python; if (PyGILState_Check()) { assert(_qore_PyRuntimeGILState_GetThreadState() == PyGILState_GetThisThreadState()); python = PyGILState_GetThisThreadState(); interpreter = python->interp; } else { python = nullptr; interpreter = _PyGILState_GetInterpreterStateUnsafe(); } owns_interpreter = false; createQoreProgram(); // insert thread state into thread map AutoLocker al(py_thr_lck); assert(py_thr_map.find(this) == py_thr_map.end()); int tid = q_gettid(); py_thr_map[this] = {{tid, {python, false}}}; py_global_tid_map[tid].insert(python); ++pgm_count; needs_deregistration = qpy_register(this); } QorePythonProgram::QorePythonProgram(QoreProgram* qpgm, QoreNamespace* pyns) : qpgm(qpgm), pyns(pyns), save_object_callback(nullptr) { printd(5, "QorePythonProgram::QorePythonProgram() this: %p GIL thread state: %p\n", this, PyGILState_GetThisThreadState()); QorePythonGilHelper qpgh; //printd(5, "QorePythonProgram::QorePythonProgram() GIL thread state: %p\n", PyGILState_GetThisThreadState()); ExceptionSink xsink; if (createInterpreter(qpgh, &xsink)) { valid = false; return; } // ensure that the __main__ module is created // returns a borrowed reference module = PyImport_AddModule("__main__"); module.py_ref(); import(&xsink, "builtins"); if (xsink) { valid = false; return; } //assert(!xsink); // returns a borrowed reference setGlobalDictionary(*module); assert(!PyErr_Occurred()); // import qoreloader module QorePythonReferenceHolder qoreloader(PyImport_ImportModule("qoreloader")); if (!qoreloader) { if (!checkPythonException(&xsink)) { xsink.raiseException("PYTHON-COMPILE-ERROR", "cannot load the 'qoreloader' module"); } return; } PyDict_SetItemString(module_dict, "qoreloader", *qoreloader); needs_deregistration = qpy_register(this); } QorePythonProgram::QorePythonProgram(const QoreString& source_code, const QoreString& source_label, int start, ExceptionSink* xsink) : save_object_callback(nullptr) { printd(5, "QorePythonProgram::QorePythonProgram() this: %p\n", this); TempEncodingHelper src_code(source_code, QCS_UTF8, xsink); if (*xsink) { xsink->appendLastDescription(" (while processing the \"source_code\" argument)"); return; } TempEncodingHelper src_label(source_label, QCS_UTF8, xsink); if (*xsink) { xsink->appendLastDescription(" (while processing the \"source_label\" argument)"); return; } //printd(5, "QorePythonProgram::QorePythonProgram() GIL thread state: %p\n", PyGILState_GetThisThreadState()); QorePythonGilHelper qpgh; //printd(5, "QorePythonProgram::QorePythonProgram() GIL thread state: %p\n", PyGILState_GetThisThreadState()); if (createInterpreter(qpgh, xsink)) { return; } // import qoreloader module QorePythonReferenceHolder qoreloader(PyImport_ImportModule("qoreloader")); if (!qoreloader) { if (!checkPythonException(xsink)) { xsink->raiseException("PYTHON-COMPILE-ERROR", "cannot load the 'qoreloader' module"); } return; } //printd(5, "QorePythonProgram::QorePythonProgram() loaded qoreloader: %p\n", *qoreloader); // parse and compile code python_code = (PyObject*)Py_CompileString(src_code->c_str(), src_label->c_str(), start); if (!python_code) { if (!checkPythonException(xsink)) { xsink->raiseException("PYTHON-COMPILE-ERROR", "parsing and compilation failed"); } return; } assert(!module); // create module for code QorePythonReferenceHolder new_module(PyImport_ExecCodeModule(src_label->c_str(), *python_code)); if (!new_module) { if (!checkPythonException(xsink)) { xsink->raiseException("PYTHON-COMPILE-ERROR", "compile failed"); } return; } module = new_module.release(); // returns a borrowed reference setGlobalDictionary(*module); PyDict_SetItemString(module_dict, "qoreloader", *qoreloader); // use the parent Program object as the source for importing qpgm = getProgram(); owns_qore_program_ref = false; pyns = qpgm->findNamespace(QORE_PYTHON_NS_NAME); assert(pyns); //printd(5, "QorePythonProgram::QorePythonProgram() this: %p pgm: %p rootns: %p\n", this, qpgm, // qpgm->getRootNS()); // create Qore program object with the same restrictions as the parent //createQoreProgram(); needs_deregistration = qpy_register(this); } int QorePythonProgram::setGlobalDictionary(PyObject* mod) { module_dict = PyModule_GetDict(mod); assert(module_dict); // returns a borrowed reference builtin_dict = PyDict_GetItemString(module_dict, "__builtins__"); assert(builtin_dict); return 0; } void QorePythonProgram::createQoreProgram() { // create Qore program object with the same restrictions as the parent QoreProgram* pgm = getProgram(); int64 parse_options = pgm ? pgm->getParseOptions64() : 0; qpgm = new QoreProgram(parse_options); owns_qore_program_ref = true; pyns = PNS->copy(); qpgm->getRootNS()->addNamespace(pyns); qpgm->setExternalData(QORE_PYTHON_MODULE_NAME, this); //printd(5, "QorePythonProgram::createQoreProgram() this: %p pgm: %p rootns: %p\n", this, qpgm, // qpgm->getRootNS()); } QorePythonProgram* QorePythonProgram::getExecutionContext() { QorePythonProgram* pypgm = reinterpret_cast(q_get_thread_local_data(python_u_tld_key)); if (pypgm && pypgm->qpgm) { return pypgm; } //printd(5, "QorePythonProgram::getExecutionContext() current pypgm context %p has no Qore Program context\n", // pypgm); return getContext(); } QorePythonProgram* QorePythonProgram::getContext() { QorePythonProgram* pypgm; // first try to get the actual Program context QoreProgram* pgm = getProgram(); if (pgm) { pypgm = static_cast(pgm->getExternalData(QORE_PYTHON_MODULE_NAME)); if (pypgm) { //printd(5, "QorePythonProgram::getContext() got local program context pgm: %p pypgm: %p\n", pgm, pypgm); return pypgm; } } pgm = qore_get_call_program_context(); if (pgm) { pypgm = static_cast(pgm->getExternalData(QORE_PYTHON_MODULE_NAME)); if (pypgm) { //printd(5, "QorePythonProgram::getContext() got local call context pgm: %p pypgm: %p\n", pgm, pypgm); return pypgm; } } //printd(5, "QorePythonProgram::getContext() got global program context pypgm: %p\n", pypgm); return qore_python_pgm; } int QorePythonProgram::staticInit() { PyDateTime_IMPORT; assert(PyDateTimeAPI); return 0; } void QorePythonProgram::waitForThreadsIntern() { while (pgm_thr_cnt) { ++pgm_thr_waiting; pgm_thr_cond.wait(py_thr_lck); --pgm_thr_waiting; } } void QorePythonProgram::deleteIntern(ExceptionSink* xsink) { if (destroyed) { return; } destroyed = true; if (needs_deregistration) { qpy_deregister(this); } printd(5, "QorePythonProgram::deleteIntern() this: %p i: %p oi: %d\n", this, interpreter, owns_interpreter); if (q_libqore_exiting()) { #ifdef DEBUG qpgm = nullptr; #endif return; } if (qpgm && owns_qore_program_ref) { // remove the external data before dereferencing qpgm->removeExternalData(QORE_PYTHON_MODULE_NAME); qpgm->waitForTerminationAndDeref(xsink); } qpgm = nullptr; // remove all thread states; the objects will be deleted by Python when the interpreter is destroyed { AutoLocker al(py_thr_lck); if (interpreter) { // wait for threads to complete before deleting entries waitForThreadsIntern(); py_thr_map_t::iterator i = py_thr_map.find(this); assert(i != py_thr_map.end()); //printd(5, "QorePythonProgram::deleteIntern() this: %p removing all thread states for pgm (delta: %d)\n", // this, (int)i->second.size()); for (auto& ti : i->second) { //printd(5, "QorePythonProgram::deleteIntern() this: %p removing TID %d\n", this, ti.first); py_global_tid_map_t::iterator gi = py_global_tid_map.find(ti.first); assert(gi != py_global_tid_map.end()); py_thr_set_t::iterator thr_i = gi->second.find(ti.second.state); if (thr_i != gi->second.end()) { gi->second.erase(thr_i); } } py_thr_map.erase(i); assert(pgm_count > 0); --pgm_count; } } if ((interpreter && owns_interpreter)) { { QorePythonHelper qph(this); for (auto& i : obj_sink) { Py_DECREF(i); } for (auto& i : meth_vec) { delete i; } meth_vec.clear(); module.purge(); python_code.purge(); for (auto& i : py_cls_map) { delete i.second; } py_cls_map.clear(); valid = false; } if (interpreter && owns_interpreter) { // grab the GIL with the main thread lock QorePythonGilHelper pgh; { // enforce serialization AutoLocker al(py_thr_lck); assert(_qore_PyRuntimeGILState_GetThreadState()); PyInterpreterState_Clear(interpreter); } PyInterpreterState_Delete(interpreter); interpreter = nullptr; owns_interpreter = false; } } if (save_object_callback) { save_object_callback = nullptr; } //printd(5, "QorePythonProgram::deleteIntern() this: %p\n", this); } QoreValue QorePythonProgram::eval(ExceptionSink* xsink, const QoreString& source_code, const QoreString& source_label, int input, bool encapsulate) { TempEncodingHelper src_code(source_code, QCS_UTF8, xsink); if (*xsink) { xsink->appendLastDescription(" (while processing the \"source_code\" argument)"); return QoreValue(); } TempEncodingHelper src_label(source_label, QCS_UTF8, xsink); if (*xsink) { xsink->appendLastDescription(" (while processing the \"source_label\" argument)"); return QoreValue(); } // ensure atomic access to the Python interpreter (GIL) and manage the Python thread state QorePythonHelper qph(this); if (checkValid(xsink)) { return QoreValue(); } QorePythonReferenceHolder return_value; QorePythonReferenceHolder python_code; { //printd(5, "QorePythonProgram::QorePythonProgram() GIL thread state: %p\n", PyGILState_GetThisThreadState()); // parse and compile code python_code = (PyObject*)Py_CompileString(src_code->c_str(), src_label->c_str(), input); if (!python_code) { if (!checkPythonException(xsink)) { xsink->raiseException("PYTHON-COMPILE-ERROR", "parsing and compilation failed"); } return QoreValue(); } } PyObject* main_dict; if (encapsulate) { // returns a borrowed reference PyObject* main = PyImport_AddModule("__main__"); // returns a borrowed reference main_dict = PyModule_GetDict(main); } else { assert(module); // returns a borrowed reference main_dict = PyModule_GetDict(*module); } return_value = PyEval_EvalCode(*python_code, main_dict, main_dict); // check for Python exceptions if (checkPythonException(xsink)) { return QoreValue(); } return getQoreValue(xsink, return_value); } void QorePythonProgram::pythonThreadCleanup(void*) { int tid = q_gettid(); //printd(5, "QorePythonProgram::pythonThreadCleanup()\n"); // issue #4651: when called when the Qore library and the python module are initialized from Java during static // process destruction, exit handlers may have already destroyed the static objects in this module if (py_thr_lck.trylock()) { return; } // delete all thread states for the tid for (auto& i : py_thr_map) { py_tid_map_t::iterator ti = i.second.find(tid); if (ti != i.second.end()) { //printd(5, "QorePythonProgram::pythonThreadCleanup() deleting state %p for TID %d", ti->second, tid); i.second.erase(ti); } } // delete reverse lookups py_global_tid_map_t::iterator gi = py_global_tid_map.find(tid); if (gi != py_global_tid_map.end()) { //printd(5, "QorePythonProgram::pythonThreadCleanup() deleting global info for TID %d", tid); py_global_tid_map.erase(gi); } //printd(5, "QorePythonProgram::pythonThreadCleanup() done\n"); py_thr_lck.unlock(); } bool QorePythonProgram::haveGil() { if (!_qore_PyCeval_GetGilLockedStatus()) { return false; } PyThreadState* tstate = _qore_PyCeval_GetThreadState(); if (!tstate) { return false; } int tid = q_gettid(); AutoLocker al(py_thr_lck); py_global_tid_map_t::iterator i = py_global_tid_map.find(tid); if (i != py_global_tid_map.end()) { return i->second.find(tstate) != i->second.end(); } return false; } bool QorePythonProgram::haveGilUnlocked(PyThreadState* check_tstate) { if (!_qore_PyCeval_GetGilLockedStatus()) { return false; } PyThreadState* tstate = _qore_PyCeval_GetThreadState(); return tstate == check_tstate; } int QorePythonProgram::createInterpreter(QorePythonGilHelper& qpgh, ExceptionSink* xsink) { assert(PyGILState_Check()); PyThreadState* python; { // enforce serialization AutoLocker al(py_thr_lck); python = Py_NewInterpreter(); if (!python) { if (xsink) { xsink->raiseException("PYTHON-COMPILE-ERROR", "error creating the Python subinterpreter"); } return -1; } assert(python->gilstate_counter == 1); //printd(5, "QorePythonProgram::createInterpreter() created thead state: %p\n", python); // NOTE: we have to reenable PyGILState_Check() here _QORE_PYTHON_REENABLE_GIL_CHECK qpgh.set(python); } interpreter = python->interp; owns_interpreter = true; printd(5, "QorePythonProgram::createInterpreter() interpreter: %p\n", interpreter); // save thread state int tid = q_gettid(); AutoLocker al(py_thr_lck); { py_thr_map_t::iterator ti = py_thr_map.lower_bound(this); if (ti == py_thr_map.end() || ti->first != this) { py_thr_map.insert(ti, {this, {{tid, {python, true}}}}); } else { ti->second[tid] = {python, true}; } } { py_global_tid_map_t::iterator i = py_global_tid_map.lower_bound(tid); if (i == py_global_tid_map.end() || i->first != tid) { py_global_tid_map.insert(i, {tid, {python}}); } else { i->second.insert(python); } //printd(5, "QorePythonProgram::createInterpreter() inserted TID %d -> %p\n", tid, python); } ++pgm_count; //printd(5, "QorePythonProgram::createInterpreter() this: %p\n", this); return setRecursionLimit(xsink); } int QorePythonProgram::getRecursionLimit() { #if QORE_VERSION_CODE > 10007 //printd(5, "QorePythonProgram::getRecursionLimit() stack remaining: %lld total size: %lld\n", // q_thread_stack_remaining(), q_thread_get_this_stack_size()); // for some reason the recusion depth needs to be calculated smaller with smaller stack sizes // testing results in using an estimated 6K python stack frame size for stacks 1M and smaller // and 10K for larger stacks int64 ss = (int64)q_thread_stack_remaining(); int64 lim; if (ss > (1024 * 1024)) { lim = (int64)q_thread_stack_remaining() / PYTHON_LARGE_STACK_FACTOR; } else { lim = (int64)q_thread_stack_remaining() / PYTHON_SMALL_STACK_FACTOR; } #else // q_thread_stack_remaining() was broken in Qore < 1.0.8 int64 lim = (int64)q_thread_get_stack_size() / PYTHON_SMALL_STACK_FACTOR; #endif return lim; } int QorePythonProgram::setRecursionLimit(ExceptionSink* xsink) { QorePythonReferenceHolder sys(PyImport_ImportModule("sys")); if (!sys) { if (!checkPythonException(xsink)) { xsink->raiseException("PYTHON-ERROR", "cannot load the 'sys' module"); } return -1; } if (!PyObject_HasAttrString(*sys, "setrecursionlimit")) { xsink->raiseException("PYTHON-ERROR", "'sys.setrecursionlimit' not found"); return -1; } QorePythonReferenceHolder func(PyObject_GetAttrString(*sys, "setrecursionlimit")); assert(PyCFunction_Check(*func)); //printd(5, "QorePythonProgram::setRecursionLimit() setrecursionlimit: %s\n", Py_TYPE(*func)->tp_name); // use the q_thread_stack_remaining() API if Qore >= 1.0.8 QorePythonReferenceHolder py_args(PyTuple_New(1)); PyTuple_SET_ITEM(*py_args, 0, PyLong_FromLongLong(getRecursionLimit())); QorePythonReferenceHolder return_value(PyCFunction_Call(*func, *py_args, nullptr)); return checkPythonException(xsink); } QorePythonThreadInfo QorePythonProgram::setContext() const { if (!valid) { return {nullptr, nullptr, nullptr, PyGILState_UNLOCKED, 0, false}; } assert(interpreter); PyThreadState* python = getAcquireThreadState(); // create new thread state if necessary if (!python) { python = PyThreadState_New(interpreter); printd(5, "QorePythonProgram::setContext() this: %p created new thread context: %p (py_thr_map: %p " "size: %d)\n", this, python, &py_thr_map, (int)py_thr_map.size()); assert(python); assert(python->gilstate_counter == 1); // the thread state will be deleted when the thread terminates or the interpreter is deleted int tid = q_gettid(); AutoLocker al(py_thr_lck); { py_thr_map_t::iterator i = py_thr_map.find(this); if (i == py_thr_map.end()) { py_thr_map[this] = {{tid, {python, owns_interpreter}}}; } else { assert(i->second.find(tid) == i->second.end()); i->second[tid] = {python, owns_interpreter}; } assert(py_thr_map.find(this) != py_thr_map.end()); } { py_global_tid_map_t::iterator i = py_global_tid_map.lower_bound(tid); if (i == py_global_tid_map.end() || i->first != tid) { py_global_tid_map.insert(i, {tid, py_thr_set_t {python,}}); } else { i->second.insert(python); } //printd(5, "QorePythonProgram::setContext() inserted TID %d -> %p\n", tid, python); } //printd(5, "QorePythonProgram::setContext() this: %p\n", this); } printd(5, "QorePythonProgram::setContext() got thread context: %p (GIL: %d hG: %d) refs: %d\n", python, PyGILState_Check(), haveGil(), python->gilstate_counter); PyGILState_STATE g_state; // the TSS state needs to be restored in any case PyThreadState* tss_state = PyGILState_GetThisThreadState(); PyThreadState* t_state, * ceval_state; // set new TSS thread state if (tss_state != python) { _qore_PyGILState_SetThisThreadState(python); } // are we currently holding the GIL? if (_qore_has_gil(tss_state)) { // set GIL context ceval_state = _qore_PyCeval_SwapThreadState(python); g_state = PyGILState_LOCKED; } else { ceval_state = nullptr; PyEval_RestoreThread(python); g_state = PyGILState_UNLOCKED; } t_state = _qore_PyRuntimeGILState_GetThreadState(); if (t_state != python) { PyThreadState_Swap(python); } // now we have the GIL assert(PyGILState_Check()); assert(haveGilUnlocked(python)); assert(_qore_PyCeval_GetThreadState() == python); assert(_qore_PyRuntimeGILState_GetThreadState() == python); // TSS state assert(PyGILState_GetThisThreadState() == python); //printd(5, "QorePythonProgram::setContext() old thread context: %p\n", t_state); ++python->gilstate_counter; // calculate new recursion depth int recursion_depth = PyThreadState_GetRecursionLimit(python); // be conservative when calculating the new recursion depth int new_recursion_depth = q_thread_stack_used() / PYTHON_SMALL_STACK_FACTOR; //printd(5, "QorePythonProgram::setContext() recursion_depth: %d -> %d\n", python->recursion_depth, // new_recursion_depth); PyThreadState_UpdateRecursionLimit(python, new_recursion_depth); return {tss_state, t_state, ceval_state, g_state, recursion_depth, true}; } void QorePythonProgram::releaseContext(const QorePythonThreadInfo& oldstate) const { if (!oldstate.valid) { return; } //struct _gilstate_runtime_state* gilstate = &_PyRuntime.gilstate; PyThreadState* python = getReleaseThreadState(); assert(python); assert(_qore_PyThreadState_IsCurrent(python)); //printd(5, "QorePythonProgram::releaseContext() rd: %d -> ord: %d\n", PyThreadState_GetRecursionLimit(python), // oldstate.recursion_depth); // restore recursion depth PyThreadState_UpdateRecursionLimit(python, oldstate.recursion_depth); // restore the old state if (oldstate.ceval_state != python) { // set GIL context _qore_PyCeval_SwapThreadState(oldstate.ceval_state); } --python->gilstate_counter; //printd(5, "QorePythonProgram::releaseContext() t_state: %p g_state: %d\n", oldstate.t_state, oldstate.g_state); if (oldstate.g_state == PyGILState_UNLOCKED) { PyEval_ReleaseThread(python); // NOTE we cannot assert !PyGILState_Check() here, as we have released the GIL, and another thread may have // created a new interpreter, which will temporarily disbale the GIL check, which would cause // PyGILState_Check() to return 1 } else { if (python != oldstate.t_state) { PyThreadState_Swap(oldstate.t_state); } } // set new TSS thread state if (oldstate.tss_state != python) { _qore_PyGILState_SetThisThreadState(oldstate.tss_state); } } PythonQoreClass* QorePythonProgram::findCreatePythonClass(const QoreClass& cls, const char* mod_name) { printd(5, "QorePythonProgram::findCreatePythonClass() %s.%s\n", mod_name, cls.getName()); py_cls_map_t::iterator i = py_cls_map.lower_bound(&cls); if (i != py_cls_map.end() && i->first == &cls) { printd(5, "QorePythonProgram::findCreatePythonClass() returning existing %s.%s\n", mod_name, cls.getName()); return i->second; } std::unique_ptr py_cls(new PythonQoreClass(this, mod_name, cls, i)); PyTypeObject* t = py_cls->getPythonType(); printd(5, "QorePythonProgram::findCreatePythonClass() returning new %s.%s type: %p (%s)\n", mod_name, cls.getName(), t, t->tp_name); //py_cls_map.insert(i, py_cls_map_t::value_type(&cls, py_cls.get())); return py_cls.release(); } struct func_capsule_t { const QoreExternalFunction& func; QorePythonProgram* py_pgm; DLLLOCAL func_capsule_t(const QoreExternalFunction& func, QorePythonProgram* py_pgm) : func(func), py_pgm(py_pgm) { } }; static void func_capsule_destructor(PyObject* func_capsule) { func_capsule_t* fc = reinterpret_cast(PyCapsule_GetPointer(func_capsule, nullptr)); delete fc; } int QorePythonProgram::importQoreFunctionToPython(PyObject* mod, const QoreExternalFunction& func) { printd(5, "QorePythonProgram::importQoreFunctionToPython() %s()\n", func.getName()); std::unique_ptr fc(new func_capsule_t(func, this)); QorePythonReferenceHolder capsule(PyCapsule_New((void*)fc.release(), nullptr, func_capsule_destructor)); std::unique_ptr funcdef(new PyMethodDef); funcdef->ml_name = func.getName(); funcdef->ml_meth = callQoreFunction; funcdef->ml_flags = METH_VARARGS; funcdef->ml_doc = nullptr; meth_vec.push_back(funcdef.get()); QorePythonReferenceHolder pyfunc(PyCFunction_New(funcdef.release(), *capsule)); assert(pyfunc); if (PyObject_SetAttrString(mod, func.getName(), *pyfunc)) { assert(PyErr_Occurred()); printd(5, "QorePythonProgram::importQoreFunctionToPython() error setting function %s in %p (%s)\n", func.getName(), mod, Py_TYPE(mod)->tp_name); return -1; } printd(5, "QorePythonProgram::importQoreFunctionToPython() ns: %p (%s) %s = %p\n", mod, Py_TYPE(mod)->tp_name, func.getName(), &func); return 0; } int QorePythonProgram::saveQoreObjectFromPython(const QoreValue& rv, ExceptionSink& xsink) { // save object in thread-local data if relevant if (rv.getType() != NT_OBJECT) { return 0; } //printd(5, "QorePythonProgram::saveQoreObjectFromPython() this: %p val: %s soc: %p\n", this, // rv.getFullTypeName(), *save_object_callback); if (save_object_callback) { ReferenceHolder args(new QoreListNode(autoTypeInfo), &xsink); args->push(rv.refSelf(), &xsink); save_object_callback->execValue(*args, &xsink); if (xsink) { raisePythonException(xsink); return -1; } return 0; } return saveQoreObjectFromPythonDefault(rv, xsink); } int QorePythonProgram::saveQoreObjectFromPythonDefault(const QoreValue& rv, ExceptionSink& xsink) { QoreHashNode* data = qpgm->getThreadData(); assert(data); const char* domain_name; // get key name where to save the data if possible QoreValue v = data->getKeyValue("_python_save"); if (v.getType() != NT_STRING) { domain_name = "_python_save"; } else { domain_name = v.get()->c_str(); } QoreValue kv = data->getKeyValue(domain_name); // ignore operation if domain exists but is not a list if (!kv || kv.getType() == NT_LIST) { QoreListNode* list; ReferenceHolder list_holder(&xsink); if (!kv) { // we need to assign list in data *after* we prepend the object to the list // in order to manage object counts list = new QoreListNode(autoTypeInfo); list_holder = list; } else { list = kv.get(); } // prepend to list to ensure FILO destruction order list->splice(0, 0, rv, &xsink); if (!xsink && list_holder) { data->setKeyValue(domain_name, list_holder.release(), &xsink); } if (xsink) { raisePythonException(xsink); return -1; } //printd(5, "saveQoreObjectFromPythonDefault() domain: '%s' obj: %p %s\n", domain_name, rv.get(), // rv.get()->getClassName()); } else { //printd(5, "saveQoreObjectFromPythonDefault() NOT SAVING domain: '%s' HAS KEY v: %s (kv: %s)\n", domain_name, // rv.getFullTypeName(), kv.getFullTypeName()); } return 0; } void QorePythonProgram::raisePythonException(ExceptionSink& xsink) { assert(xsink); QoreValue err(xsink.getExceptionErr()); QoreValue desc(xsink.getExceptionDesc()); QoreValue arg(xsink.getExceptionArg()); ExceptionSink xsink2; QorePythonReferenceHolder tuple(PyTuple_New(arg ? 3 : 2)); QorePythonReferenceHolder ex_arg(getPythonValue(err, &xsink2)); if (ex_arg) { PyTuple_SET_ITEM(*tuple, 0, ex_arg.release()); } else { Py_INCREF(Py_None); PyTuple_SET_ITEM(*tuple, 0, Py_None); } ex_arg = getPythonValue(desc, &xsink2); if (ex_arg) { PyTuple_SET_ITEM(*tuple, 1, ex_arg.release()); } else { Py_INCREF(Py_None); PyTuple_SET_ITEM(*tuple, 1, Py_None); } if (arg) { ex_arg = getPythonValue(arg, &xsink2); if (ex_arg) { PyTuple_SET_ITEM(*tuple, 2, ex_arg.release()); } else { Py_INCREF(Py_None); PyTuple_SET_ITEM(*tuple, 2, Py_None); } } xsink.clear(); ex_arg = PyObject_CallObject((PyObject*)&PythonQoreException_Type, *tuple); //printd(5, "QorePythonProgram::raisePythonException() py_ex: %p %s\n", *ex_arg, Py_TYPE(*ex_arg)->tp_name); PyErr_SetObject((PyObject*)&PythonQoreException_Type, *ex_arg); } // import Qore definitions to Python void QorePythonProgram::importQoreToPython(PyObject* mod, const QoreNamespace& ns, const char* mod_name) { //printd(5, "QorePythonProgram::importQoreToPython() mod: %p (%d) ns: %p '%s' mod name: '%s'\n", mod, // Py_REFCNT(mod), &ns, ns.getName(), mod_name); // import all functions QoreNamespaceFunctionIterator fi(ns); while (fi.next()) { const QoreExternalFunction& func = fi.get(); // do not import deprecated functions if (func.getCodeFlags() & QCF_DEPRECATED) { continue; } if (importQoreFunctionToPython(mod, func)) { return; } } // import all constants QoreNamespaceConstantIterator consti(ns); while (consti.next()) { if (importQoreConstantToPython(mod, consti.get())) { return; } } // import all classes QoreNamespaceClassIterator clsi(ns); while (clsi.next()) { if (importQoreClassToPython(mod, clsi.get(), mod_name)) { return; } } // import all subnamespaces as modules QoreNamespaceNamespaceIterator ni(ns); while (ni.next()) { if (importQoreNamespaceToPython(mod, ni.get())) { return; } } } int QorePythonProgram::importQoreConstantToPython(PyObject* mod, const QoreExternalConstant& constant) { //printd(5, "QorePythonProgram::importQoreConstantToPython() %s\n", constant.getName()); ExceptionSink xsink; ValueHolder qoreval(constant.getReferencedValue(), &xsink); if (!xsink) { QorePythonReferenceHolder val(qore_python_pgm->getPythonValue(*qoreval, &xsink)); if (!xsink) { if (!PyObject_SetAttrString(mod, constant.getName(), *val)) { return 0; } } } raisePythonException(xsink); return -1; } int QorePythonProgram::importQoreClassToPython(PyObject* mod, const QoreClass& cls, const char* mod_name) { PyTypeObject* py_cls = findCreatePythonClass(cls, mod_name)->getPythonType(); //printd(5, "QorePythonProgram::importQoreClassToPython() py_cls: %p ('%s') cn: '%s' mod_name: '%s'\n", py_cls, // py_cls->tp_name, cls.getName(), mod_name); PyObject* clsobj = (PyObject*)py_cls; if (PyObject_SetAttrString(mod, cls.getName(), clsobj)) { return -1; } return 0; } int QorePythonProgram::importQoreNamespaceToPython(PyObject* mod, const QoreNamespace& ns) { //printd(5, "QorePythonProgram::importQoreNamespaceToPython() %s\n", ns.getName()); assert(PyModule_Check(mod)); QoreStringMaker nsname("%s.%s", PyModule_GetName(mod), ns.getName()); // create a submodule QorePythonReferenceHolder new_mod(newModule(nsname.c_str(), &ns)); printd(5, "QorePythonProgram::importQoreNamespaceToPython() (mod) created new module '%s'\n", nsname.c_str()); importQoreToPython(*new_mod, ns, nsname.c_str()); if (PyObject_SetAttrString(mod, ns.getName(), *new_mod)) { return -1; } return 0; } PyObject* QorePythonProgram::newModule(const char* name, const QoreNamespace* ns_pkg) { //QorePythonReferenceHolder new_mod(PyModule_New(name)); QorePythonReferenceHolder new_mod(ModuleNamespace_New(name, ns_pkg)); if (ns_pkg) { std::string nspath = ns_pkg->getPath(); QorePythonReferenceHolder path(PyUnicode_FromStringAndSize(nspath.c_str(), nspath.size())); PyObject_SetAttrString(*new_mod, "__path__", *path); } saveModule(name, *new_mod); return new_mod.release(); } PyObject* QorePythonProgram::newModule(const char* name, const char* path) { QorePythonReferenceHolder new_mod(PyModule_New(name)); QorePythonReferenceHolder py_path(PyUnicode_FromStringAndSize(path, strlen(path))); PyObject_SetAttrString(*new_mod, "__path__", *py_path); saveModule(name, *new_mod); return new_mod.release(); } void QorePythonProgram::importQoreNamespaceToPython(const QoreNamespace& ns, const QoreString& py_mod_path, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::getCreateModule() '%s'\n", path); assert(!py_mod_path.empty()); QorePythonHelper qph(this); if (checkValid(xsink)) { return; } assert(module); // start with the root module module.py_ref(); QorePythonReferenceHolder mod(*module); strvec_t strpath = get_dot_path_list(py_mod_path.c_str()); assert(!strpath.empty()); QoreString nspath; for (const std::string& str : strpath) { bool store = nspath.empty(); if (!store) { nspath.concat('.'); } nspath.concat(str); //printd(5, "QorePythonProgram::importQoreNamespaceToPython() '%s' str: '%s' mod: %p ('%s')\n", // py_mod_path.c_str(), str.c_str(), *mod, Py_TYPE(*mod)->tp_name); if (PyObject_HasAttrString(*mod, str.c_str())) { QorePythonReferenceHolder new_mod(PyObject_GetAttrString(*mod, str.c_str())); if (PyModule_Check(*new_mod) || PyDict_Check(*new_mod)) { mod = new_mod.release(); continue; } // WARNING: any existing attribute will be replaced with the new module } QorePythonReferenceHolder new_mod(newModule(nspath.c_str(), &ns)); //printd(5, "QorePythonProgram::importQoreNamespaceToPython() created new module '%s' (store: %d)\n", // nspath.c_str(), store); assert(new_mod); if (PyObject_SetAttrString(*mod, str.c_str(), *new_mod)) { if (!checkPythonException(xsink)) { xsink->raiseException("IMPORT-NS-ERROR", "could not set element '%s' when creating path '%s'", str.c_str(), py_mod_path.c_str()); } return; } mod = new_mod.release(); } printd(5, "QorePythonProgram::importQoreNamespaceToPython() %s => %p ('%s': %s) mr: %d\n", py_mod_path.c_str(), *mod, Py_TYPE(*mod)->tp_name, ns.getName(), Py_REFCNT(*mod)); assert(mod); importQoreToPython(*mod, ns, strpath.back().c_str()); checkPythonException(xsink); } void QorePythonProgram::aliasDefinition(const QoreString& source_path, const QoreString& target_path) { ExceptionSink xsink; QorePythonHelper qph(this); if (checkValid(&xsink)) { throw QoreXSinkException(xsink); } if (!module) { throw QoreStandardException("PYTHON-ALIAS-ERROR", "source path '%s' cannot be found as there is no code " "context", source_path.c_str()); } // start with the root Dict module.py_ref(); QorePythonReferenceHolder source_obj(*module); strvec_t strpath = get_dot_path_list(source_path.c_str()); assert(!strpath.empty()); for (size_t i = 0, e = strpath.size(); i < e; ++i) { const std::string& str = strpath[i]; if (!PyObject_HasAttrString(*source_obj, str.c_str())) { throw QoreStandardException("PYTHON-ALIAS-ERROR", "source path '%s': element '%s' not found", source_path.c_str(), str.c_str()); } source_obj = PyObject_GetAttrString(*source_obj, str.c_str()); assert(source_obj); } // get / create target module.py_ref(); QorePythonReferenceHolder obj(*module); strpath = get_dot_path_list(target_path.c_str()); assert(!strpath.empty()); QoreString nspath; for (size_t i = 0, e = strpath.size(); i < e; ++i) { const std::string& str = strpath[i]; if (!nspath.empty()) { nspath.concat('.'); } nspath.concat(str); //printd(5, "QorePythonProgram::aliasDefinition() '%s' str: '%s' obj: %p ('%s')\n", target_path.c_str(), // str.c_str(), *obj, Py_TYPE(*obj)->tp_name); QorePythonReferenceHolder new_elem; if (i < (e - 1)) { if (PyObject_HasAttrString(*obj, str.c_str())) { obj = PyObject_GetAttrString(*obj, str.c_str()); continue; } // create a new module, if the parent is a module, or a dictionary QoreStringMaker path("alias:%s", source_path.c_str()); new_elem = newModule(nspath.c_str(), path.c_str()); //printd(5, "QorePythonProgram::aliasDefinition() created module '%s'\n", nspath.c_str()); } else { new_elem = source_obj.release(); //printd(5, "QorePythonProgram::aliasDefinition() got elem %p %s (path %s)\n", *new_elem, // Py_TYPE(*new_elem)->tp_name, nspath.c_str()); // if aliasing a module, insert the new alias in sys.modules if (PyModule_Check(*new_elem)) { saveModule(nspath.c_str(), *new_elem); } } if (PyObject_SetAttrString(*obj, str.c_str(), *new_elem) < 0) { ExceptionSink xsink; if (!checkPythonException(&xsink)) { new QoreStandardException("PYTHON-ALIAS-ERROR", "could not set element '%s' when creating " \ "target path '%s' while aliasing source path '%s'", str.c_str(), target_path.c_str(), source_path.c_str()); } throw QoreXSinkException(xsink); } if (i == (e - 1)) { break; } obj = new_elem.release(); } } void QorePythonProgram::exportClass(ExceptionSink* xsink, QoreString& arg) { QorePythonHelper qph(this); if (checkValid(xsink)) { return; } // start with the root module module.py_ref(); QorePythonReferenceHolder obj(*module); strvec_t strpath = get_dot_path_list(arg.c_str()); assert(!strpath.empty()); for (const std::string& str : strpath) { //printd(5, "QorePythonProgram::exportClass() '%s' str: '%s' mod: %p ('%s')\n", arg.c_str(), str.c_str(), mod, // Py_TYPE(mod)->tp_name); if (!PyObject_HasAttrString(*obj, str.c_str())) { xsink->raiseException("EXPORT-CLASS-ERROR", "could find component '%s' in path '%s'", str.c_str(), arg.c_str()); return; } obj = PyObject_GetAttrString(*obj, str.c_str()); } if (!PyType_Check(*obj)) { xsink->raiseException("EXPORT-CLASS-ERROR", "path '%s' is not a class; got type '%s' instead", arg.c_str(), Py_TYPE(*obj)->tp_name); return; } PyTypeObject* type = reinterpret_cast(*obj); clmap_t::iterator i = clmap.lower_bound(type); if (i != clmap.end() && i->first == type) { xsink->raiseException("EXPORT-CLASS-ERROR", "Qore class for Python path '%s' already exists", arg.c_str()); return; } qore_offset_t ci = arg.rfind('.'); //QoreExternalProgramContextHelper pch(xsink, qpgm); // grab current Program's parse lock before manipulating namespaces CurrentProgramRuntimeExternalParseContextHelper pch; QoreNamespace* ns = qpgm->getRootNS(); if (ci > 0) { QoreString ns_str(arg); ns_str.terminate(ci); ns_str.replaceAll(".", "::"); ns = ns->findCreateNamespacePathAll(ns_str.c_str()); } strset_t nsset; addClassToNamespaceIntern(xsink, ns, (PyTypeObject*)*obj, strpath.back().c_str(), i, nsset); } void QorePythonProgram::addModulePath(ExceptionSink* xsink, QoreString& arg) { q_env_subst(arg); printd(5, "QorePythonProgram::addModulePath() this: %p arg: '%s'\n", this, arg.c_str()); QorePythonHelper qph(this); if (checkValid(xsink)) { return; } QorePythonReferenceHolder sys(PyImport_ImportModule("sys")); if (!sys) { if (!checkPythonException(xsink)) { throw QoreStandardException("PYTHON-ERROR", "cannot load 'sys' module"); } return; } QorePythonReferenceHolder path; if (!PyObject_HasAttrString(*sys, "path")) { path = PyList_New(0); } else { path = PyObject_GetAttrString(*sys, "path"); if (!PyList_Check(*path)) { throw QoreStandardException("PYTHON-ERROR", "'sys.path' is not a list; got type '%s' instead", Py_TYPE(*path)->tp_name); } } QorePythonReferenceHolder item(PyUnicode_FromStringAndSize(arg.c_str(), arg.size())); PyList_Append(*path, *item); } void QorePythonProgram::exportFunction(ExceptionSink* xsink, QoreString& arg) { QorePythonHelper qph(this); if (checkValid(xsink)) { return; } // start with the root module module.py_ref(); QorePythonReferenceHolder obj(*module); strvec_t strpath = get_dot_path_list(arg.c_str()); assert(!strpath.empty()); for (const std::string& str : strpath) { //printd(5, "QorePythonProgram::exportClass() '%s' str: '%s' mod: %p ('%s')\n", arg.c_str(), str.c_str(), mod, // Py_TYPE(mod)->tp_name); if (!PyObject_HasAttrString(*obj, str.c_str())) { xsink->raiseException("EXPORT-FUNCTION-ERROR", "could find component '%s' in path '%s'", str.c_str(), arg.c_str()); return; } obj = PyObject_GetAttrString(*obj, str.c_str()); } q_external_func_t qore_func = nullptr; if (PyFunction_Check(*obj)) { qore_func = (q_external_func_t)QorePythonProgram::execPythonFunction; } else if (PyCFunction_Check(*obj)) { qore_func = (q_external_func_t)QorePythonProgram::execPythonCFunction; } else { xsink->raiseException("EXPORT-FUNCTION-ERROR", "path '%s' is not a function; got type '%s' instead", arg.c_str(), Py_TYPE(*obj)->tp_name); return; } flmap_t::iterator i = flmap.lower_bound(*obj); if (i != flmap.end() && i->first == *obj) { xsink->raiseException("EXPORT-FUNCTION-ERROR", "Qore function for Python path '%s' already exists", arg.c_str()); return; } qore_offset_t ci = arg.rfind('.'); //QoreExternalProgramContextHelper pch(xsink, qpgm); // grab current Program's parse lock before manipulating namespaces CurrentProgramRuntimeExternalParseContextHelper pch; QoreNamespace* ns = qpgm->getRootNS(); if (ci > 0) { QoreString ns_str(arg); ns_str.terminate(ci); ns_str.replaceAll(".", "::"); ns = ns->findCreateNamespacePathAll(ns_str.c_str()); } const char* func_name = strpath.back().c_str(); if (findCreateQoreFunction(*obj, func_name, qore_func)) { xsink->raiseException("EXPORT-FUNCTION-ERROR", "Qore function for Python path '%s' already exists", arg.c_str()); } } QoreListNode* QorePythonProgram::getQoreListFromList(ExceptionSink* xsink, PyObject* val) { pyobj_set_t rset; return getQoreListFromList(xsink, val, rset); } QoreListNode* QorePythonProgram::getQoreListFromList(ExceptionSink* xsink, PyObject* val, pyobj_set_t& rset) { assert(PyList_Check(val)); ReferenceHolder rv(new QoreListNode(autoTypeInfo), xsink); Py_ssize_t len = PyList_Size(val); for (Py_ssize_t i = 0; i < len; ++i) { ValueHolder qval(getQoreValue(xsink, PyList_GetItem(val, i), rset), xsink); if (*xsink) { return nullptr; } rv->push(qval.release(), xsink); assert(!*xsink); } return rv.release(); } QoreListNode* QorePythonProgram::getQoreListFromTuple(ExceptionSink* xsink, PyObject* val, size_t offset, bool for_args) { pyobj_set_t rset; return getQoreListFromTuple(xsink, val, rset, offset); } QoreListNode* QorePythonProgram::getQoreListFromTuple(ExceptionSink* xsink, PyObject* val, pyobj_set_t& rset, size_t offset, bool for_args) { assert(PyTuple_Check(val)); Py_ssize_t len = PyTuple_Size(val); if (for_args && !len) { return nullptr; } ReferenceHolder rv(new QoreListNode(autoTypeInfo), xsink); for (Py_ssize_t i = offset; i < len; ++i) { ValueHolder qval(getQoreValue(xsink, PyTuple_GetItem(val, i), rset), xsink); if (*xsink || checkPythonException(xsink)) { return nullptr; } rv->push(qval.release(), xsink); assert(!*xsink); } return rv.release(); } QoreHashNode* QorePythonProgram::getQoreHashFromDict(ExceptionSink* xsink, PyObject* val, pyobj_set_t& rset) { assert(PyDict_Check(val)); assert(rset.find(val) == rset.end()); rset.insert(val); ReferenceHolder rv(new QoreHashNode(autoTypeInfo), xsink); PyObject* key, * value; Py_ssize_t pos = 0; while (PyDict_Next(val, &pos, &key, &value)) { const char* keystr; QorePythonReferenceHolder tkey; if (Py_TYPE(key) == &PyUnicode_Type) { keystr = PyUnicode_AsUTF8(key); } else { tkey = PyObject_Repr(key); assert(Py_TYPE(*tkey) == &PyUnicode_Type); keystr = PyUnicode_AsUTF8(*tkey); } // skip recursive refs if (PyDict_Check(value) && (rset.find(value) != rset.end())) { continue; } ValueHolder qval(getQoreValue(xsink, value, rset), xsink); if (*xsink) { return nullptr; } rv->setKeyValue(keystr, qval.release(), xsink); } return rv.release(); } BinaryNode* QorePythonProgram::getQoreBinaryFromBytes(PyObject* val) { assert(PyBytes_Check(val)); SimpleRefHolder rv(new BinaryNode); rv->append(PyBytes_AS_STRING(val), PyBytes_GET_SIZE(val)); return rv.release(); } BinaryNode* QorePythonProgram::getQoreBinaryFromByteArray(PyObject* val) { assert(PyByteArray_Check(val)); SimpleRefHolder rv(new BinaryNode); rv->append(PyByteArray_AsString(val), PyByteArray_Size(val)); return rv.release(); } DateTimeNode* QorePythonProgram::getQoreDateTimeFromDelta(PyObject* val) { assert(PyDelta_Check(val)); return DateTimeNode::makeRelative(0, 0, PyDateTime_DELTA_GET_DAYS(val), 0, 0, PyDateTime_DELTA_GET_SECONDS(val), PyDateTime_DELTA_GET_MICROSECONDS(val)); } DateTimeNode* QorePythonProgram::getQoreDateTimeFromDateTime(PyObject* val) { assert(PyDateTime_Check(val)); const AbstractQoreZoneInfo* zone = nullptr; if (PyObject_HasAttrString(val, "tzinfo")) { // get UTC offset for time QorePythonReferenceHolder tzinfo(PyObject_GetAttrString(val, "tzinfo")); if (tzinfo && PyTZInfo_Check(*tzinfo)) { assert(PyObject_HasAttrString(*tzinfo, "utcoffset")); QorePythonReferenceHolder utcoffset_func(PyObject_GetAttrString(*tzinfo, "utcoffset")); assert(PyCallable_Check(*utcoffset_func)); QorePythonReferenceHolder args(PyTuple_New(1)); Py_INCREF(val); PyTuple_SET_ITEM(*args, 0, val); QorePythonReferenceHolder delta(PyEval_CallObject(*utcoffset_func, *args)); if (delta && PyDelta_Check(*delta)) { zone = findCreateOffsetZone(PyDateTime_DELTA_GET_SECONDS(*delta)); //printd(5, "TZ RV: %p '%s' utcoffset: %d (%p)\n", *delta, Py_TYPE(*delta)->tp_name, // PyDateTime_DELTA_GET_SECONDS(*delta), zone); } } } return DateTimeNode::makeAbsolute(zone ? zone : currentTZ(), PyDateTime_GET_YEAR(val), PyDateTime_GET_MONTH(val), PyDateTime_GET_DAY(val), PyDateTime_DATE_GET_HOUR(val), PyDateTime_DATE_GET_MINUTE(val), PyDateTime_DATE_GET_SECOND(val), PyDateTime_DATE_GET_MICROSECOND(val)); } DateTimeNode* QorePythonProgram::getQoreDateTimeFromDate(PyObject* val) { assert(PyDate_Check(val)); return DateTimeNode::makeAbsolute(currentTZ(), PyDateTime_GET_YEAR(val), PyDateTime_GET_MONTH(val), PyDateTime_GET_DAY(val), 0, 0, 0, 0); } DateTimeNode* QorePythonProgram::getQoreDateTimeFromTime(PyObject* val) { assert(PyDateTime_Check(val)); return DateTimeNode::makeAbsolute(currentTZ(), 0, 0, 0, PyDateTime_TIME_GET_HOUR(val), PyDateTime_TIME_GET_MINUTE(val), PyDateTime_TIME_GET_SECOND(val), PyDateTime_TIME_GET_MICROSECOND(val)); } ResolvedCallReferenceNode* QorePythonProgram::getQoreCallRefFromFunc(ExceptionSink* xsink, PyObject* val) { assert(PyFunction_Check(val)); Py_INCREF(val); weakRef(); return new PythonCallableCallReferenceNode(this, val); } ResolvedCallReferenceNode* QorePythonProgram::getQoreCallRefFromMethod(ExceptionSink* xsink, PyObject* val) { assert(PyMethod_Check(val)); PyMethodObject* m = reinterpret_cast(val); Py_INCREF(m->im_func); Py_INCREF(m->im_self); weakRef(); // NOTE: classmethods will have their "self" arg = the class / type return new PythonCallableCallReferenceNode(this, m->im_func, PyType_Check(m->im_self) ? nullptr : m->im_self); } QoreValue QorePythonProgram::getQoreValue(ExceptionSink* xsink, QorePythonReferenceHolder& val) { return getQoreValue(xsink, *val); } QoreValue QorePythonProgram::getQoreValue(ExceptionSink* xsink, QorePythonReferenceHolder& val, pyobj_set_t& rset) { return getQoreValue(xsink, *val, rset); } QoreValue QorePythonProgram::getQoreValue(ExceptionSink* xsink, PyObject* val) { pyobj_set_t rset; return getQoreValue(xsink, val, rset); } QoreValue QorePythonProgram::getQoreValue(ExceptionSink* xsink, PyObject* val, pyobj_set_t& rset) { printd(5, "QorePythonBase::getQoreValue() this: %p PyDateTimeAPI: %p val: %p '%s'\n", this, PyDateTimeAPI, val, Py_TYPE(val)->tp_name); assert(PyDateTimeAPI); if (!val || val == Py_None) { return QoreValue(); } // if this is already a Qore object, then return it if (PyQoreObject_Check(val)) { PyQoreObject* pyobj = reinterpret_cast(val); return pyobj->qobj ? pyobj->qobj->refSelf() : QoreValue(); } PyTypeObject* type = Py_TYPE(val); if (type == &PyBool_Type) { return QoreValue(val == Py_True); } if (type == &PyLong_Type) { // Python 3+ implements "long" as an arbitrary-precision number (= Qore "number") QorePythonReferenceHolder longval(PyObject_Repr(val)); assert(Py_TYPE(*longval) == &PyUnicode_Type); const char* longstr = PyUnicode_AsUTF8(*longval); // see if we can convert to a Qore integer bool sign = longstr[0] == '-'; size_t len = strlen(longstr); if (len < 19 || (len == 19 && ((!sign && strcmp(longstr, "9223372036854775807") <= 0) || (sign && strcmp(longstr, "-9223372036854775808") <= 0)))) { return strtoll(longstr, 0, 10); } return new QoreNumberNode(longstr); } if (type == &PyFloat_Type) { return QoreValue(PyFloat_AS_DOUBLE(val)); } if (type == &PyUnicode_Type) { Py_ssize_t size; const char* str = PyUnicode_AsUTF8AndSize(val, &size); return new QoreStringNode(str, size, QCS_UTF8); } if (type == &PyList_Type) { return getQoreListFromList(xsink, val, rset); } if (type == &PyTuple_Type) { return getQoreListFromTuple(xsink, val, rset); } if (type == &PyBytes_Type) { return getQoreBinaryFromBytes(val); } if (type == &PyByteArray_Type) { return getQoreBinaryFromByteArray(val); } if (type == PyDateTimeAPI->DateType) { return getQoreDateTimeFromDate(val); } if (type == PyDateTimeAPI->TimeType) { return getQoreDateTimeFromTime(val); } if (type == PyDateTimeAPI->DateTimeType) { return getQoreDateTimeFromDateTime(val); } if (type == PyDateTimeAPI->DeltaType) { return getQoreDateTimeFromDelta(val); } if (type == &PyDict_Type) { return getQoreHashFromDict(xsink, val, rset); } if (PyFunction_Check(val)) { return getQoreCallRefFromFunc(xsink, val); } if (PyMethod_Check(val)) { return getQoreCallRefFromMethod(xsink, val); } QoreClass* cls = getCreateQorePythonClass(xsink, type); if (!cls) { assert(*xsink); return QoreValue(); } Py_INCREF(val); QoreObject* obj = new QoreObject(cls, qpgm, new QorePythonPrivateData(val)); //printd(5, "QorePythonProgram::getQoreValue() obj: %p cls: %p '%s' id: %d\n", obj, cls, cls->getName(), // cls->getID()); return obj; } QoreValue QorePythonProgram::getQoreAttr(PyObject* obj, const char* attr, ExceptionSink* xsink) { QorePythonReferenceHolder return_value(PyObject_GetAttrString(obj, attr)); // check for Python exceptions if (!return_value && checkPythonException(xsink)) { return QoreValue(); } return getQoreValue(xsink, *return_value); } PyObject* QorePythonProgram::getPythonList(ExceptionSink* xsink, const QoreListNode* l) { QorePythonReferenceHolder list(PyList_New(l->size())); ConstListIterator i(l); while (i.next()) { QorePythonReferenceHolder val(getPythonValue(i.getValue(), xsink)); if (*xsink) { raisePythonException(*xsink); return nullptr; } PyList_SetItem(*list, i.index(), val.release()); } return list.release(); } PyObject* QorePythonProgram::getPythonTupleValue(ExceptionSink* xsink, const QoreListNode* l, size_t arg_offset, PyObject* first) { //printd(5, "QorePythonProgram::getPythonTupleValue() l: %d first: %p\n", l ? (int)l->size() : 0, first); bool has_list = (l && l->size() >= arg_offset); if (!first && !has_list) { return PyTuple_New(0); } Py_ssize_t size = has_list ? (l->size() - arg_offset) : 0; if (first) { ++size; } QorePythonReferenceHolder tuple(PyTuple_New(size)); size_t offset = 0; if (first) { Py_INCREF(first); PyTuple_SET_ITEM(*tuple, 0, first); offset = 1; } if (has_list) { ConstListIterator i(l, arg_offset - 1); while (i.next()) { QorePythonReferenceHolder val(getPythonValue(i.getValue(), xsink)); if (*xsink) { raisePythonException(*xsink); return nullptr; } PyTuple_SET_ITEM(*tuple, i.index() - arg_offset + offset, val.release()); } } return tuple.release(); } PyObject* QorePythonProgram::getPythonDict(ExceptionSink* xsink, const QoreHashNode* h) { QorePythonReferenceHolder dict(PyDict_New()); ConstHashIterator i(h); while (i.next()) { QorePythonReferenceHolder key(getPythonString(xsink, i.getKeyString())); if (*xsink) { raisePythonException(*xsink); return nullptr; } QorePythonReferenceHolder val(getPythonValue(i.get(), xsink)); if (*xsink) { raisePythonException(*xsink); return nullptr; } assert(val); PyDict_SetItem(*dict, *key, *val); } return dict.release(); } PyObject* QorePythonProgram::getPythonString(ExceptionSink* xsink, const QoreString* str) { TempEncodingHelper py_str(str, QCS_UTF8, xsink); if (*xsink) { qore_python_pgm->raisePythonException(*xsink); return nullptr; } return PyUnicode_FromStringAndSize(py_str->c_str(), py_str->size()); } PyObject* QorePythonProgram::getPythonByteArray(ExceptionSink* xsink, const BinaryNode* b) { return PyByteArray_FromStringAndSize(reinterpret_cast(b->getPtr()), b->size()); } PyObject* QorePythonProgram::getPythonDelta(ExceptionSink* xsink, const DateTime* dt) { assert(dt->isRelative()); // WARNING: years are converted to 365 days; months are converted to 30 days return PyDelta_FromDSU(dt->getYear() * 365 + dt->getMonth() * 30 + dt->getDay(), dt->getHour() * 3600 + dt->getMinute() * 60 + dt->getSecond(), dt->getMicrosecond()); } PyObject* QorePythonProgram::getPythonDateTime(ExceptionSink* xsink, const DateTime* dt) { assert(dt->isAbsolute()); return PyDateTime_FromDateAndTime(dt->getYear(), dt->getMonth(), dt->getDay(), dt->getHour(), dt->getMinute(), dt->getSecond(), dt->getMicrosecond()); } PyObject* QorePythonProgram::getPythonCallable(ExceptionSink* xsink, const ResolvedCallReferenceNode* call) { QorePythonImplicitQoreArgHelper qpiqoh((void*)call); return PyObject_CallObject((PyObject*)&PythonQoreCallable_Type, nullptr); } PyObject* QorePythonProgram::getPythonValue(QoreValue val, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::getPythonValue() type '%s'\n", val.getFullTypeName()); switch (val.getType()) { case NT_NOTHING: case NT_NULL: Py_INCREF(Py_None); return Py_None; case NT_BOOLEAN: { PyObject* rv = val.getAsBool() ? Py_True : Py_False; Py_INCREF(rv); return rv; } case NT_INT: return PyLong_FromLongLong(val.getAsBigInt()); case NT_FLOAT: return PyFloat_FromDouble(val.getAsFloat()); case NT_STRING: return getPythonString(xsink, val.get()); case NT_LIST: return getPythonList(xsink, val.get()); case NT_HASH: return getPythonDict(xsink, val.get()); case NT_BINARY: return getPythonByteArray(xsink, val.get()); case NT_DATE: { const DateTimeNode* dt = val.get(); return dt->isRelative() ? getPythonDelta(xsink, dt) : getPythonDateTime(xsink, dt); } case NT_RUNTIME_CLOSURE: case NT_FUNCREF: { return getPythonCallable(xsink, val.get()); } case NT_OBJECT: { QoreObject* o = const_cast(val.get()); if (!o->isValid()) { Py_INCREF(Py_None); return Py_None; } TryPrivateDataRefHolder pypd(o, CID_PYTHONBASEOBJECT, xsink); if (pypd) { PyObject* rv = pypd->get(); Py_XINCREF(rv); return rv; } PythonQoreClass* py_cls = findCreatePythonClass(*o->getClass(), "qore"); return py_cls->wrap(o); } } // ignore types that cannot be converted to a Python value and return None Py_INCREF(Py_None); return Py_None; } QoreValue QorePythonProgram::callFunction(ExceptionSink* xsink, const QoreString& func_name, const QoreListNode* args, size_t arg_offset) { assert(!haveGil()); TempEncodingHelper fname(func_name, QCS_UTF8, xsink); if (*xsink) { xsink->appendLastDescription(" (while processing the \"func_name\" argument)"); return QoreValue(); } // set Qore program context for Qore APIs QoreExternalProgramContextHelper pch(xsink, qpgm); if (*xsink) { return QoreValue(); } ValueHolder rv(xsink); { QorePythonHelper qph(this); if (checkValid(xsink)) { return QoreValue(); } // returns a borrowed reference PyObject* py_func = PyDict_GetItemString(module_dict, fname->c_str()); if (!py_func || !PyFunction_Check(py_func)) { xsink->raiseException("NO-FUNCTION", "cannot find function '%s'", fname->c_str()); return QoreValue(); } //printd(5, "QorePythonProgram::callFunction() this: %p %s()\n", this, func_name.c_str()); rv = callInternal(xsink, py_func, args, arg_offset); } assert(!haveGil()); return rv.release(); } QoreValue QorePythonProgram::callMethod(ExceptionSink* xsink, const QoreString& class_name, const QoreString& method_name, const QoreListNode* args, size_t arg_offset) { TempEncodingHelper cname(class_name, QCS_UTF8, xsink); if (*xsink) { xsink->appendLastDescription(" (while processing the \"class_name\" argument)"); return QoreValue(); } TempEncodingHelper mname(method_name, QCS_UTF8, xsink); if (*xsink) { xsink->appendLastDescription(" (while processing the \"method_name\" argument)"); return QoreValue(); } return callMethod(xsink, cname->c_str(), mname->c_str(), args, arg_offset); } QoreValue QorePythonProgram::callMethod(ExceptionSink* xsink, const char* cname, const char* mname, const QoreListNode* args, size_t arg_offset, PyObject* first) { // set Qore program context for Qore APIs QoreExternalProgramContextHelper pch(xsink, qpgm); if (*xsink) { return QoreValue(); } QorePythonHelper qph(this); if (checkValid(xsink)) { return QoreValue(); } assert(module_dict); assert(builtin_dict); // returns a borrowed reference PyObject* py_class = PyDict_GetItemString(module_dict, cname); if (!py_class || !PyType_Check(py_class)) { // returns a borrowed reference py_class = PyDict_GetItemString(builtin_dict, cname); if (!py_class || !PyType_Check(py_class)) { xsink->raiseException("NO-CLASS", "cannot find class '%s'", cname); return QoreValue(); } } // returns a borrowed reference QorePythonReferenceHolder py_method; if (PyObject_HasAttrString(py_class, mname)) { py_method = PyObject_GetAttrString(py_class, mname); } if (!py_method || (!PyFunction_Check(*py_method) && (Py_TYPE(*py_method) != &PyMethodDescr_Type))) { xsink->raiseException("NO-METHOD", "cannot find method '%s.%s()'", cname, mname); return QoreValue(); } //printd(5, "QorePythonProgram::callMethod() %s::%s() args: %d\n", cname, mname, args ? (int)args->size() : 0); return callInternal(xsink, *py_method, args, arg_offset); } QoreValue QorePythonProgram::callInternal(ExceptionSink* xsink, PyObject* callable, const QoreListNode* args, size_t arg_offset, PyObject* first) { // set Qore program context for Qore APIs QoreExternalProgramContextHelper pch(xsink, qpgm); if (*xsink) { return QoreValue(); } QorePythonHelper qph(this); if (checkValid(xsink)) { return QoreValue(); } //printd(5, "QorePythonProgram::callInternal() f: %p args: %d (%d) self: %p\n", callable, // args ? (int)args->size() : 0, (int)arg_offset, first); QorePythonReferenceHolder rv(callPythonInternal(xsink, callable, args, arg_offset, first)); return *xsink ? QoreValue() : getQoreValue(xsink, rv.release()); } PyObject* QorePythonProgram::callPythonInternal(ExceptionSink* xsink, PyObject* callable, const QoreListNode* args, size_t arg_offset, PyObject* first, PyObject* kwargs) { QorePythonReferenceHolder py_args; py_args = getPythonTupleValue(xsink, args, arg_offset, first); if (*xsink) { return nullptr; } //printd(5, "QorePythonProgram::callPythonInternal(): this: %p valid: %d argcount: %d (first: %p)\n", this, valid, // (args && args->size() > arg_offset) ? args->size() - arg_offset : 0, first); QorePythonReferenceHolder return_value(PyEval_CallObjectWithKeywords(callable, *py_args, kwargs)); // check for Python exceptions if (!return_value && checkPythonException(xsink)) { return nullptr; } return return_value.release(); } QoreValue QorePythonProgram::callFunctionObject(ExceptionSink* xsink, PyObject* func, const QoreListNode* args, size_t arg_offset, PyObject* first) { // set Qore program context for Qore APIs QoreExternalProgramContextHelper pch(xsink, qpgm); if (*xsink) { return QoreValue(); } QorePythonHelper qph(this); if (checkValid(xsink)) { return QoreValue(); } QorePythonReferenceHolder py_args(getPythonTupleValue(xsink, args, arg_offset, first)); if (*xsink) { return QoreValue(); } //printd(5, "QorePythonProgram::callFunctionObject(): this: %p valid: %d argcount: %d\n", this, valid, // (args && args->size() > arg_offset) ? args->size() - arg_offset : 0); QorePythonReferenceHolder return_value(PyFunction_Type.tp_call(func, *py_args, nullptr)); // check for Python exceptions if (!return_value && checkPythonException(xsink)) { return QoreValue(); } return getQoreValue(xsink, return_value.release()); } void QorePythonProgram::clearPythonException() { QorePythonReferenceHolder ex_type, ex_value, traceback; PyErr_Fetch(ex_type.getRef(), ex_value.getRef(), traceback.getRef()); } int QorePythonProgram::checkPythonException(ExceptionSink* xsink) { // returns a borrowed reference PyObject* ex = PyErr_Occurred(); if (!ex) { //printd(5, "QorePythonProgram::checkPythonException() no error\n"); return 0; } QorePythonReferenceHolder ex_type, ex_value, traceback; PyErr_Fetch(ex_type.getRef(), ex_value.getRef(), traceback.getRef()); assert(ex_type); // get location QoreExternalProgramLocationWrapper loc; QoreCallStack callstack; printd(5, "QorePythonProgram::checkPythonException() type: %s val: %s (%p) traceback: %s\n", Py_TYPE(*ex_type)->tp_name, ex_value ? Py_TYPE(*ex_value)->tp_name : "(null)", *ex_value, traceback ? Py_TYPE(*traceback)->tp_name : "(null)"); if (!ex_value) { ex_value = Py_None; Py_INCREF(Py_None); } if (!traceback) { traceback = Py_None; Py_INCREF(Py_None); } PyErr_NormalizeException(ex_type.getRef(), ex_value.getRef(), traceback.getRef()); printd(5, "QorePythonProgram::checkPythonException() type: %s val: %s (%p) traceback: %s\n", Py_TYPE(*ex_type)->tp_name, Py_TYPE(*ex_value)->tp_name, *ex_value, Py_TYPE(*traceback)->tp_name); bool use_loc; if (PyTraceBack_Check(*traceback)) { PyTracebackObject* tb = reinterpret_cast(*traceback); int depth = 0; PyTracebackObject* tb1 = tb; while (tb1) { ++depth; tb1 = tb1->tb_next; } while (tb->tb_next && depth > 0) { --depth; tb = tb->tb_next; } PyFrameObject* frame = tb->tb_frame; const char* funcname = nullptr; #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 10 while (frame) { // get line number QorePythonReferenceHolder code((PyObject*)PyFrame_GetCode(frame)); int line = PyCode_Addr2Line((PyCodeObject*)*code, PyFrame_GetLasti(frame)); QorePythonReferenceHolder filename_obj(PyObject_GetAttrString(*code, "co_filename")); const char* filename; Py_INCREF(*filename_obj); QorePythonReferenceHolder np_obj(QorePythonStackLocationHelper::normalizePath(*filename_obj)); if (np_obj) { filename = getCString(*np_obj); } else { filename = PyUnicode_AsUTF8(*filename_obj); } if (frame == tb->tb_frame) { loc.set(filename, line, line, nullptr, 0, QORE_PYTHON_LANG_NAME); } else { callstack.add(CT_USER, filename, line, line, funcname, QORE_PYTHON_LANG_NAME); } QorePythonReferenceHolder funcname_obj(PyObject_GetAttrString(*code, "co_qualname")); funcname = PyUnicode_AsUTF8(*funcname_obj); frame = PyFrame_GetBack(frame); } #else while (frame) { int line = PyCode_Addr2Line(frame->f_code, frame->f_lasti); const char* filename; Py_INCREF(frame->f_code->co_filename); QorePythonReferenceHolder np_obj(QorePythonStackLocationHelper::normalizePath( frame->f_code->co_filename )); if (np_obj) { filename = getCString(*np_obj); } else { filename = getCString(frame->f_code->co_filename); } if (frame == tb->tb_frame) { loc.set(filename, line, line, nullptr, 0, QORE_PYTHON_LANG_NAME); } else { callstack.add(CT_USER, filename, line, line, funcname, QORE_PYTHON_LANG_NAME); } funcname = getCString(frame->f_code->co_name); frame = frame->f_back; } #endif if (funcname) { callstack.add(CT_BUILTIN, this_file, __LINE__, __LINE__, funcname, "c++"); } use_loc = true; } else { use_loc = false; } // check if it's a QoreException if (*ex_type == (PyObject*)&PythonQoreException_Type) { assert(PyObject_HasAttrString(*ex_value, "err")); QorePythonReferenceHolder pyval(PyObject_GetAttrString(*ex_value, "err")); ValueHolder err(getQoreValue(xsink, *pyval), xsink); assert(err->getType() == NT_STRING); if (!*xsink) { ValueHolder desc(xsink); if (PyObject_HasAttrString(*ex_value, "desc")) { pyval = PyObject_GetAttrString(*ex_value, "desc"); desc = getQoreValue(xsink, *pyval); } if (!*xsink) { assert(!desc || desc->getType() == NT_STRING); ValueHolder arg(xsink); if (PyObject_HasAttrString(*ex_value, "arg")) { pyval = PyObject_GetAttrString(*ex_value, "arg"); arg = getQoreValue(xsink, *pyval); } if (!*xsink) { QoreStringValueHelper errstr(*err); QoreStringNodeValueHelper descstr(*desc); if (use_loc) { xsink->raiseExceptionArg(loc.get(), errstr->c_str(), arg->refSelf(), descstr.getReferencedValue(), callstack); } else { xsink->raiseExceptionArg(errstr->c_str(), arg->refSelf(), descstr.getReferencedValue(), callstack); } return -1; } } } } if (!*xsink) { // get full exception class name PyTypeObject* py_cls = Py_TYPE(*ex_value); QoreString ex_name(py_cls->tp_name); if (PyObject_HasAttrString(reinterpret_cast(py_cls), "__module__")) { QorePythonReferenceHolder ex_mod(PyObject_GetAttrString(reinterpret_cast(py_cls), "__module__")); if (PyUnicode_Check(*ex_mod)) { ex_name.prepend("."); ex_name.prepend(PyUnicode_AsUTF8(*ex_mod)); } } // get description QorePythonReferenceHolder desc(PyObject_Str(*ex_value)); ValueHolder qore_desc(getQoreValue(xsink, *desc), xsink); if (!*xsink) { QoreStringNodeValueHelper descstr(*qore_desc); if (use_loc) { // check if we have a Qore exception xsink->raiseExceptionArg(loc.get(), ex_name.c_str(), QoreValue(), descstr.getReferencedValue(), callstack); } else { // check if we have a Qore exception xsink->raiseExceptionArg(ex_name.c_str(), QoreValue(), descstr.getReferencedValue(), callstack); } return -1; } } xsink->appendLastDescription(" (while trying to convert Python exception arguments to Qore)"); return -1; } QoreValue QorePythonProgram::callPythonMethod(ExceptionSink* xsink, PyObject* attr, PyObject* obj, const QoreListNode* args, size_t arg_offset) { PyTypeObject* mtype = Py_TYPE(attr); //printd(5, "callPythonMethod() '%s' '%s'\n", mtype->tp_name, Py_TYPE(obj)->tp_name); // check for static method if (mtype == &PyStaticMethod_Type) { // get callable from static method QorePythonReferenceHolder py_method(PyStaticMethod_Type.tp_descr_get(attr, nullptr, nullptr)); assert(py_method); return callInternal(xsink, *py_method, args, arg_offset); } // check for wrapper descriptors -> normal method if (mtype == &PyWrapperDescr_Type) { return callWrapperDescriptorMethod(xsink, obj, attr, args, arg_offset); } if (mtype == &PyMethodDescr_Type) { return callMethodDescriptorMethod(xsink, obj, attr, args, arg_offset); } if (mtype == &PyClassMethodDescr_Type) { return callClassMethodDescriptorMethod(xsink, obj, attr, args, arg_offset); } if (PyFunction_Check(attr)) { return callFunctionObject(xsink, attr, args, arg_offset, obj); } if (PyCFunction_Check(attr)) { return callCFunctionMethod(xsink, attr, args, arg_offset); } xsink->raiseException("PYTHON-ERROR", "cannot make a call with Python type '%s'", mtype->tp_name); return QoreValue(); } QoreClass* QorePythonProgram::getCreateQorePythonClass(ExceptionSink* xsink, PyTypeObject* type, int flags) { // grab current Program's parse lock before manipulating namespaces CurrentProgramRuntimeExternalParseContextHelper pch; if (!pch) { xsink->raiseException("PROGRAM-ERROR", "cannot process Python type '%s' in deleted Program object", type->tp_name); return nullptr; } strset_t nsset; return getCreateQorePythonClassIntern(xsink, type, nsset, nullptr, flags); } QoreNamespace* QorePythonProgram::getNamespaceForObject(PyObject* obj) { //printd(5, "QorePythonProgram::getNamespaceForObject() obj: %p (%s)\n", obj, Py_TYPE(obj)->tp_name); QoreString ns_path; // use the __name__ attribute to derive the namespace path if possible if (PyObject_HasAttrString(obj, "__name__")) { QorePythonReferenceHolder name(PyObject_GetAttrString(obj, "__name__")); const char* name_str = PyUnicode_AsUTF8(*name); //printd(5, "QorePythonProgram::getNamespaceForObject() obj %p __name__ '%s'\n", obj, name_str); const char* p = strrchr(name_str, '.'); if (p) { ns_path = name_str; ns_path.terminate(p - name_str); ns_path.replaceAll(".", "::"); } } // otherwise use "module_context", if not available, use the __module__ attribute, if available if (!module_context && ns_path.empty() && PyObject_HasAttrString(obj, "__module__")) { QorePythonReferenceHolder mod(PyObject_GetAttrString(obj, "__module__")); if (PyUnicode_Check(*mod)) { const char* mod_str = PyUnicode_AsUTF8(*mod); //printd(5, "QorePythonProgram::getNamespaceForObject() obj %p __module__ '%s'\n", obj, mod_str); ns_path = mod_str; ns_path.replaceAll(".", "::"); } } if (ns_path.empty()) { //printd(5, "QorePythonProgram::getNamespaceForObject() obj %p (%s) -> ns: Python\n", obj, // Py_TYPE(obj)->tp_name); if (!module_context) { return pyns; } ns_path = module_context; ns_path.replaceAll(".", "::"); } //printd(5, "QorePythonProgram::getNamespaceForObject() obj %p (%s) -> ns: Python::%s\n", obj, // Py_TYPE(obj)->tp_name, ns_path.c_str()); QoreNamespace* ns = pyns->findCreateNamespacePathAll(ns_path.c_str()); #if QORE_VERSION_CODE >= 10013 if (module_context) { if (ns->setKeyValueIfNotSet("python_module", module_context)) { printd(5, "set python_module: '%s' in ns: '%s'\n", module_context, ns->getPath(true).c_str()); } } #endif return ns; } QoreClass* QorePythonProgram::getCreateQorePythonClassIntern(ExceptionSink* xsink, PyTypeObject* type, strset_t& nsset, const char* cname, int flags) { //printd(5, "QorePythonProgram::getCreateQorePythonClassIntern() class: '%s'\n", type->tp_name); // see if the Python type already represents a Qore class if (PyQoreObjectType_Check(type)) { //printd(5, "QorePythonProgram::getCreateQorePythonClassIntern() class: '%s' is Qore\n", type->tp_name); return const_cast(PythonQoreClass::getQoreClass(type)); } //printd(5, "QorePythonProgram::getCreateQorePythonClassIntern() creating Qore class for Python class: '%s' \n", // type->tp_name); clmap_t::iterator i = clmap.lower_bound(type); if (i != clmap.end() && i->first == type) { return i->second; } // get relative path to class and class name QoreString rpath_str; if (!cname) { const char* p = strrchr(type->tp_name, '.'); if (p) { rpath_str.set(type->tp_name, p - type->tp_name); cname = p + 1; } else { cname = type->tp_name; } } // create new QorePythonClass QoreNamespace* ns = getNamespaceForObject(reinterpret_cast(type)); if (!rpath_str.empty() && rpath_str.startsWith(ns->getName())) { size_t len = strlen(ns->getName()); if (rpath_str[len] == '.' && rpath_str.size() > len) { rpath_str.replace(0, len + 1, nullptr); rpath_str.replaceAll(".", "::"); ns = ns->findCreateNamespacePathAll(rpath_str.c_str()); } } return addClassToNamespaceIntern(xsink, ns, type, cname, i, nsset, flags); } QorePythonClass* QorePythonProgram::addClassToNamespaceIntern(ExceptionSink* xsink, QoreNamespace* ns, PyTypeObject* type, const char* cname, clmap_t::iterator i, strset_t& nsset, int flags) { // get a unique name for the class QoreString cname_str = cname; // namespace path std::string ns_path = ns->getPath(true); // full path of class with ns std::string full_path; strset_t::iterator nsi; { int base = 0; while (true) { full_path = ns_path + "::" + cname_str.c_str(); nsi = nsset.find(full_path); if (nsi == nsset.end() && !ns->findLocalClass(cname_str.c_str())) { break; } cname_str.clear(); cname_str.sprintf("%s_base_%d", cname, base++); } } cname = cname_str.c_str(); // create new class std::unique_ptr cls(new QorePythonClass(this, cname, full_path.c_str())); // add to nsset nsset.insert(nsi, full_path); // insert into map clmap.insert(i, clmap_t::value_type(type, cls.get())); //printd(5, "QorePythonProgram::addClassToNamespaceIntern() ns: '%s' cls: '%s' (%s id: %d)\n", ns->getName(), // cls->getName(), type->tp_name, cls->getID()); return setupQorePythonClass(xsink, ns, type, cls, nsset, flags); } static constexpr int static_meth_flags = QCF_USES_EXTRA_ARGS; static constexpr int normal_meth_flags = static_meth_flags | QCF_ABSTRACT_OVERRIDE_ALL; QorePythonClass* QorePythonProgram::setupQorePythonClass(ExceptionSink* xsink, QoreNamespace* ns, PyTypeObject* type, std::unique_ptr& cls, strset_t& nsset, int flags) { //printd(5, "QorePythonProgram::setupQorePythonClass() ns: '%s' cls: '%s' (%s) flags: %d\n", ns->getName(), // cls->getName(), type->tp_name, flags); cls->addConstructor((void*)type, (q_external_constructor_t)execPythonConstructor, Public, QCF_USES_EXTRA_ARGS, QDOM_UNCONTROLLED_API); cls->setDestructor((void*)type, (q_external_destructor_t)execPythonDestructor); // create Python mapping for QoreClass if necessary if (!PyQoreObjectType_Check(type)) { new PythonQoreClass(this, type, *cls.get()); } // add single base class if (type->tp_base) { QoreClass* bclass = getCreateQorePythonClassIntern(xsink, type->tp_base, nsset); if (!bclass) { assert(*xsink); return nullptr; } printd(5, "QorePythonProgram::setupQorePythonClass() %s (id: %d) parent: %s (bclass: %p id: %d)\n", type->tp_name, cls->getID(), type->tp_base->tp_name, bclass, bclass->getID()); cls->addBaseClass(bclass, true); } cls->addBuiltinVirtualBaseClass(QC_PYTHONBASEOBJECT); ns->addSystemClass(cls.get()); printd(5, "QorePythonProgram::setupQorePythonClass() %s methods: %p\n", type->tp_name, type->tp_methods); // process dict if (type->tp_dict) { PyObject* key, * value; Py_ssize_t pos = 0; while (PyDict_Next(type->tp_dict, &pos, &key, &value)) { assert(Py_TYPE(key) == &PyUnicode_Type); const char* keystr = PyUnicode_AsUTF8(key); PyTypeObject* var_type = Py_TYPE(value); // check for static method if (var_type == &PyStaticMethod_Type) { // get callable from static method // returns a new reference PyObject* py_method = PyStaticMethod_Type.tp_descr_get(value, nullptr, nullptr); assert(py_method); cls->addObj(py_method); cls->addStaticMethod((void*)py_method, keystr, (q_external_static_method_t)QorePythonProgram::execPythonStaticMethod, Public, static_meth_flags, QDOM_UNCONTROLLED_API, autoTypeInfo); printd(5, "QorePythonProgram::setupQorePythonClass() added static method " \ "%s.%s() (%s)\n", type->tp_name, keystr, Py_TYPE(value)->tp_name); continue; } // check for wrapper descriptors -> normal method if (var_type == &PyWrapperDescr_Type) { Py_INCREF(value); cls->addObj(value); if (!strcmp(keystr, "copy")) { keystr = "_copy"; } cls->addMethod((void*)value, keystr, (q_external_method_t)QorePythonProgram::execPythonNormalWrapperDescriptorMethod, Public, normal_meth_flags, QDOM_UNCONTROLLED_API, autoTypeInfo); printd(5, "QorePythonProgram::setupQorePythonClass() added normal wrapper " \ "descriptor method %s.%s() (%s) %p: %d\n", type->tp_name, keystr, Py_TYPE(value)->tp_name, value, value->ob_refcnt); continue; } // check for method descriptors -> normal method if (var_type == &PyMethodDescr_Type) { Py_INCREF(value); cls->addObj(value); if (!strcmp(keystr, "copy")) { keystr = "_copy"; } cls->addMethod((void*)value, keystr, (q_external_method_t)QorePythonProgram::execPythonNormalMethodDescriptorMethod, Public, normal_meth_flags, QDOM_UNCONTROLLED_API, autoTypeInfo); printd(5, "QorePythonProgram::setupQorePythonClass() added normal method " \ "descriptor method %s.%s() (%s) %p: %d\n", type->tp_name, keystr, Py_TYPE(value)->tp_name, value, value->ob_refcnt); continue; } // check for classmethod descriptors -> normal method if (var_type == &PyClassMethodDescr_Type) { // do not need to save reference here if (!strcmp(keystr, "copy")) { keystr = "_copy"; } cls->addMethod((void*)value, keystr, (q_external_method_t)QorePythonProgram::execPythonNormalClassMethodDescriptorMethod, Public, normal_meth_flags, QDOM_UNCONTROLLED_API, autoTypeInfo); printd(5, "QorePythonProgram::setupQorePythonClass() added normal " "classmethod descriptor method %s.%s() (%s)\n", type->tp_name, keystr, Py_TYPE(value)->tp_name); continue; } // check for normal user methods if (PyFunction_Check(value)) { Py_INCREF(value); cls->addObj(value); if (!strcmp(keystr, "copy")) { keystr = "_copy"; } cls->addMethod((void*)value, keystr, (q_external_method_t)QorePythonProgram::execPythonNormalMethod, Public, normal_meth_flags, QDOM_UNCONTROLLED_API, autoTypeInfo); printd(5, "QorePythonProgram::setupQorePythonClass() added normal method " \ "%s.%s() (%s)\n", type->tp_name, keystr, Py_TYPE(value)->tp_name); continue; } // check for builtin functions -> static method if (PyCFunction_Check(value)) { // do not need to save reference here cls->addStaticMethod((void*)value, keystr, (q_external_static_method_t)QorePythonProgram::execPythonStaticCFunctionMethod, Public, static_meth_flags, QDOM_UNCONTROLLED_API, autoTypeInfo); printd(5, "QorePythonProgram::setupQorePythonClass() added static C function method " \ "%s.%s() (%s)\n", type->tp_name, keystr, Py_TYPE(value)->tp_name); continue; } // check for member descriptors if (var_type == &PyMemberDescr_Type) { cls->addPythonMember(keystr, reinterpret_cast(value)->d_member); continue; } printd(5, "QorePythonProgram::setupQorePythonClass() %s: member '%s': %s\n", type->tp_name, keystr, Py_TYPE(value)->tp_name); } } return cls.release(); } QoreValue QorePythonProgram::execPythonStaticCFunctionMethod(const QoreMethod& meth, PyObject* func, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::execPythonStaticCFunctionMethod() m: %s::%s() argcount: %d\n", meth.getClassName(), // meth.getName(), args ? (int)args->size() : 0); QorePythonProgram* pypgm = QorePythonProgram::getPythonProgramFromMethod(meth, xsink); return pypgm->callCFunctionMethod(xsink, func, args); } QoreValue QorePythonProgram::execPythonCFunction(PyObject* func, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::execPythonCFunction() argcount: %d\n", args ? (int)args->size() : 0); QorePythonProgram* pypgm = QorePythonProgram::getContext(); return pypgm->callCFunctionMethod(xsink, func, args); } QoreValue QorePythonProgram::execPythonFunction(PyObject* func, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::execPythonFunction() argcount: %d\n", args ? (int)args->size() : 0); QorePythonProgram* pypgm = QorePythonProgram::getContext(); return pypgm->callFunctionObject(xsink, func, args); } QoreValue QorePythonProgram::callCFunctionMethod(ExceptionSink* xsink, PyObject* func, const QoreListNode* args, size_t arg_offset) { // set Qore program context for Qore APIs QoreExternalProgramContextHelper pch(xsink, qpgm); if (*xsink) { return QoreValue(); } QorePythonHelper qph(this); QorePythonReferenceHolder py_args; if (checkValid(xsink)) { return QoreValue(); } py_args = getPythonTupleValue(xsink, args, arg_offset); if (*xsink) { return QoreValue(); } //printd(5, "QorePythonProgram::callCMethod(): argcount: %d\n", // (args && args->size() > arg_offset) ? args->size() - arg_offset : 0); QorePythonReferenceHolder return_value(PyCFunction_Call(func, *py_args, nullptr)); // check for Python exceptions if (!return_value && checkPythonException(xsink)) { return QoreValue(); } return getQoreValue(xsink, *return_value); } void QorePythonProgram::execPythonConstructor(const QoreMethod& meth, PyObject* pycls, QoreObject* self, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { QorePythonProgram* pypgm = QorePythonProgram::getPythonProgramFromMethod(meth, xsink); // set Qore program context for Qore APIs QoreExternalProgramContextHelper pch(xsink, pypgm->qpgm); if (*xsink) { return; } QorePythonHelper qph(pypgm); if (pypgm->checkValid(xsink)) { return; } assert(PyType_Check(pycls)); //printd(5, "QorePythonProgram::execPythonConstructor() m: %s args: %d self: %s\n", meth.getName(), // args ? (int)args->size() : 0, self->getClassName()); // save Qore object for any Python class that needs it QorePythonImplicitQoreArgHelper qpiqoh(self); QorePythonReferenceHolder pyobj(pypgm->callPythonInternal(xsink, pycls, args)); if (*xsink) { return; } // check base class initialization self->setPrivate(meth.getClass()->getID(), new QorePythonPrivateData(pyobj.release())); } void QorePythonProgram::execPythonDestructor(const QorePythonClass& thisclass, PyObject* pycls, QoreObject* self, QorePythonPrivateData* pd, ExceptionSink* xsink) { if (q_libqore_exiting()) { return; } QorePythonProgram* pypgm = thisclass.getPythonProgram(); QorePythonHelper qph(pypgm); // FIXME: cannot delete objects after the python program has been destroyed if (pypgm->valid) { pd->deref(xsink); } } QoreValue QorePythonProgram::execPythonStaticMethod(const QoreMethod& meth, PyObject* m, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { QorePythonProgram* pypgm = QorePythonProgram::getPythonProgramFromMethod(meth, xsink); return pypgm->callInternal(xsink, m, args); } QoreValue QorePythonProgram::execPythonNormalMethod(const QoreMethod& meth, PyObject* m, QoreObject* self, QorePythonPrivateData* pd, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::execPythonNormalMethod() %s::%s() pyobj: %p: %d\n", meth.getClassName(), // meth.getName(), m, m->ob_refcnt); QorePythonProgram* pypgm = QorePythonProgram::getPythonProgramFromMethod(meth, xsink); return pypgm->callInternal(xsink, m, args, 0, pd->get()); } QoreValue QorePythonProgram::execPythonNormalWrapperDescriptorMethod(const QoreMethod& meth, PyObject* m, QoreObject* self, QorePythonPrivateData* pd, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::execPythonNormalWrapperDescriptorMethod() %s::%s() pyobj: %p: %d\n", // meth.getClassName(), meth.getName(), m, m->ob_refcnt); assert(m->ob_refcnt > 0); QorePythonProgram* pypgm = QorePythonProgram::getPythonProgramFromMethod(meth, xsink); return pypgm->callWrapperDescriptorMethod(xsink, pd->get(), m, args); } QoreValue QorePythonProgram::execPythonNormalMethodDescriptorMethod(const QoreMethod& meth, PyObject* m, QoreObject* self, QorePythonPrivateData* pd, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::execPythonNormalMethodDescriptorMethod() %s::%s() pyobj: %p: %d\n", // meth.getClassName(), meth.getName(), m, m->ob_refcnt); QorePythonProgram* pypgm = QorePythonProgram::getPythonProgramFromMethod(meth, xsink); return pypgm->callMethodDescriptorMethod(xsink, pd->get(), m, args); } QoreValue QorePythonProgram::execPythonNormalClassMethodDescriptorMethod(const QoreMethod& meth, PyObject* m, QoreObject* self, QorePythonPrivateData* pd, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::execPythonNormalClassMethodDescriptorMethod() %s::%s() pyobj: %p: %d\n", // meth.getClassName(), meth.getName(), m, m->ob_refcnt); QorePythonProgram* pypgm = QorePythonProgram::getPythonProgramFromMethod(meth, xsink); return pypgm->callClassMethodDescriptorMethod(xsink, pd->get(), m, args); } QoreValue QorePythonProgram::callWrapperDescriptorMethod(ExceptionSink* xsink, PyObject* self, PyObject* obj, const QoreListNode* args, size_t arg_offset) { QorePythonHelper qph(this); if (checkValid(xsink)) { return QoreValue(); } QorePythonReferenceHolder py_args; py_args = getPythonTupleValue(xsink, args, arg_offset, self); if (*xsink) { return QoreValue(); } //printd(5, "QorePythonProgram::callWrapperDescriptorMethod() argcount: %d\n", // (args && args->size() > arg_offset) ? args->size() - arg_offset : 0); QorePythonReferenceHolder return_value(PyWrapperDescr_Type.tp_call(obj, *py_args, nullptr)); // check for Python exceptions if (!return_value && checkPythonException(xsink)) { return QoreValue(); } return getQoreValue(xsink, *return_value); } QoreValue QorePythonProgram::callMethodDescriptorMethod(ExceptionSink* xsink, PyObject* self, PyObject* obj, const QoreListNode* args, size_t arg_offset) { QorePythonHelper qph(this); if (checkValid(xsink)) { return QoreValue(); } QorePythonReferenceHolder py_args; py_args = getPythonTupleValue(xsink, args, arg_offset, self); if (*xsink) { return QoreValue(); } //printd(5, "QorePythonProgram::callMethodDescriptorMethod() argcount: %d\n", // (args && args->size() > arg_offset) ? args->size() - arg_offset : 0); QorePythonReferenceHolder return_value(PyMethodDescr_Type.tp_call(obj, *py_args, nullptr)); // check for Python exceptions if (!return_value && checkPythonException(xsink)) { return QoreValue(); } return getQoreValue(xsink, *return_value); } QoreValue QorePythonProgram::callClassMethodDescriptorMethod(ExceptionSink* xsink, PyObject* self, PyObject* obj, const QoreListNode* args, size_t arg_offset) { QorePythonHelper qph(this); if (checkValid(xsink)) { return QoreValue(); } // get class from self QorePythonReferenceHolder cls(PyObject_GetAttrString(self, "__class__")); QorePythonReferenceHolder py_args; py_args = getPythonTupleValue(xsink, args, arg_offset, *cls); if (*xsink) { return QoreValue(); } //printd(5, "QorePythonProgram::callClassMethodDescriptorMethod(): argcount: %d\n", // (args && args->size() > arg_offset) ? args->size() - arg_offset : 0); QorePythonReferenceHolder return_value(PyClassMethodDescr_Type.tp_call(obj, *py_args, nullptr)); // check for Python exceptions if (!return_value && checkPythonException(xsink)) { return QoreValue(); } return getQoreValue(xsink, *return_value); } int QorePythonProgram::saveModule(const char* name, PyObject* mod) { QorePythonReferenceHolder sys(PyImport_ImportModule("sys")); if (!sys) { return -1; } if (!PyObject_HasAttrString(*sys, "modules")) { return -1; } QorePythonReferenceHolder modules(PyObject_GetAttrString(*sys, "modules")); printd(5, "QorePythonProgram::saveModule() modules: %p '%s' %s\n", *modules, name, Py_TYPE(*modules)->tp_name); if (!PyDict_Check(*modules)) { return -1; } return PyDict_SetItemString(*modules, name, mod); } int QorePythonProgram::import(ExceptionSink* xsink, const char* module, const char* symbol) { //printd(5, "QorePythonProgram::import() module: '%s' symbol: '%s'\n", module, symbol ? symbol : "n/a"); QoreString mod_name(module); QorePythonReferenceHolder mod; if (symbol) { QoreString sym(symbol); qore_offset_t i = sym.rfind('.'); if (i > 0 && (size_t)i != (sym.size() - 1)) { mod_name.concat('.'); mod_name.concat(sym, 0, i, xsink); //printd(5, "trying to import '%s'\n", mod_name.c_str()); mod = PyImport_ImportModule(mod_name.c_str()); if (!mod) { if (!checkPythonException(xsink)) { throw QoreStandardException("PYTHON-IMPORT-ERROR", "Python could not load module '%s'", module); } return -1; } // if the module has already been imported, then ignore if (mod_set.find(*mod) != mod_set.end()) { return 0; } QoreString ns_path(mod_name); ns_path.replaceAll(".", "::"); symbol = sym.c_str() + i + 1; //printd(5, "imported module: '%s' importing symbol: '%s' (ns_path: '%s')\n", mod_name.c_str(), symbol, // ns_path.c_str()); PythonModuleContextHelper mch(this, ns_path.c_str()); // https://docs.python.org/3/reference/import.html: // any module that contains a __path__ attribute is considered a package return checkImportSymbol(xsink, mod_name.c_str(), *mod, PyObject_HasAttrString(*mod, "__path__"), symbol, IF_ALL, false); } } mod = PyImport_ImportModule(module); if (!mod) { if (!checkPythonException(xsink)) { throw QoreStandardException("PYTHON-IMPORT-ERROR", "Python could not load module '%s'", module); } return -1; } // if the module has already been imported, then ignore if (mod_set.find(*mod) != mod_set.end()) { return 0; } return importModule(xsink, *mod, module, IF_ALL); } int QorePythonProgram::importModule(ExceptionSink* xsink, PyObject* mod, const char* module, int filter) { PythonModuleContextHelper mch(this, module); // if the module has already been imported, then ignore if (mod_set.find(mod) != mod_set.end()) { return 0; } mod_set.insert(mod); PyObject* main = PyImport_AddModule("__main__"); assert(main); Py_INCREF(mod); if (PyModule_AddObject(main, module, mod) < 0) { Py_DECREF(mod); if (!checkPythonException(xsink)) { xsink->raiseException("PYTHON-IMPORT-ERROR", "module '%s' could not be added to the main module", module); } return -1; } // returns a borrowed reference PyObject* mod_dict = PyModule_GetDict(mod); if (!mod_dict) { // no dictionary; cannot import module throw QoreStandardException("PYTHON-IMPORT-ERROR", "module '%s' has no dictionary", module); } // https://docs.python.org/3/reference/import.html: // any module that contains a __path__ attribute is considered a package bool is_package = (bool)PyDict_GetItemString(mod_dict, "__path__"); //printd(5, "QorePythonProgram::importModule() '%s' mod: %p (%d) pkg: %d (def: %p)\n", module, mod, filter, // is_package, PyModule_GetDef(mod)); // check the dictionary for __all__, giving a list of strings as public symbols // returns a borrowed reference { PyObject* all = PyDict_GetItemString(mod_dict, "__all__"); if (all && PyTuple_Check(all)) { Py_ssize_t len = PyTuple_Size(all); for (Py_ssize_t i = 0; i < len; ++i) { // returns a borrowed reference PyObject* sv = PyTuple_GetItem(all, i); if (!sv || !PyUnicode_Check(sv)) { throw QoreStandardException("PYTHON-IMPORT-ERROR", "module '%s' __all__ has an invalid " \ "element with type '%s'; expecting 'str'", module, sv ? Py_TYPE(sv)->tp_name : "null"); } if (checkImportSymbol(xsink, module, mod, is_package, PyUnicode_AsUTF8(sv), filter, true)) { return -1; } } printd(5, "QorePythonProgram::importModule() '%s' mod: %p (%d) pkg: %d imported __all__: %d\n", module, mod, filter, is_package, len); return 0; } } QorePythonReferenceHolder dir(PyObject_Dir(mod)); if (dir && PyList_Check(*dir)) { Py_ssize_t len = PyList_Size(*dir); for (Py_ssize_t i = 0; i < len; ++i) { // returns a borrowed reference PyObject* sv = PyList_GetItem(*dir, i); if (!sv || !PyUnicode_Check(sv)) { throw QoreStandardException("PYTHON-IMPORT-ERROR", "module '%s' __all__ has an invalid " \ "element with type '%s'; expecting 'str'", module, sv ? Py_TYPE(sv)->tp_name : "null"); } if (checkImportSymbol(xsink, module, mod, is_package, PyUnicode_AsUTF8(sv), filter, true)) { return -1; } } printd(5, "QorePythonProgram::importModule() '%s' mod: %p (%d) pkg: %d imported dir: %d\n", module, mod, filter, is_package, len); return 0; } throw QoreStandardException("PYTHON-IMPORT-ERROR", "module '%s' has no symbol directory", module); } int QorePythonProgram::checkImportSymbol(ExceptionSink* xsink, const char* module, PyObject* mod, bool is_package, const char* symbol, int filter, bool ignore_missing) { if (!PyObject_HasAttrString(mod, symbol)) { if (ignore_missing) { return 0; } throw QoreStandardException("PYTHON-IMPORT-ERROR", "module '%s' references unknown symbol '%s'", module, symbol); } QorePythonReferenceHolder value(PyObject_GetAttrString(mod, symbol)); assert(value); bool is_class = PyType_Check(*value); if (is_class) { if (!(filter & IF_CLASS)) { return 0; } } else { bool is_module = PyModule_Check(*value); if (is_module) { if (!is_package) { return 0; } } else if (!(filter & IF_OTHER)) { return 0; } } return importSymbol(xsink, *value, module, symbol, filter); } int QorePythonProgram::findCreateQoreFunction(PyObject* value, const char* symbol, q_external_func_t func) { QoreNamespace* ns = getNamespaceForObject(value); if (!ns->findLocalFunction(symbol)) { // do not need to save reference here ns->addBuiltinVariant((void*)value, symbol, func, QCF_USES_EXTRA_ARGS, QDOM_UNCONTROLLED_API, autoTypeInfo); printd(5, "QorePythonProgram::findCreateQoreFunction() added function %s::%s() (%s)\n", ns->getName(), symbol, Py_TYPE(value)->tp_name); return 0; } return -1; } int QorePythonProgram::importSymbol(ExceptionSink* xsink, PyObject* value, const char* module, const char* symbol, int filter) { printd(5, "QorePythonProgram::importSymbol() %s.%s (type %s)\n", module, symbol, Py_TYPE(value)->tp_name); // check for builtin functions -> static method if (PyCFunction_Check(value)) { // ignore errors findCreateQoreFunction(value, symbol, (q_external_func_t)QorePythonProgram::execPythonCFunction); return 0; } if (PyFunction_Check(value)) { // ignore errors findCreateQoreFunction(value, symbol, (q_external_func_t)QorePythonProgram::execPythonFunction); return 0; } if (PyType_Check(value)) { //printd(5, "QorePythonProgram::importSymbol() class sym: '%s' -> '%s' (%p)\n", symbol, // reinterpret_cast(value)->tp_name, value); strset_t nsset; getCreateQorePythonClassIntern(xsink, reinterpret_cast(value), nsset); if (*xsink) { return -1; } //printd(5, "QorePythonProgram::importSymbol() added class %s.%s (%s)\n", module, symbol, cls->getName()); return 0; } if (PyModule_Check(value)) { QoreStringMaker sub_module("%s::%s", module, symbol); return importModule(xsink, value, sub_module.c_str(), filter); } //printd(5, "QorePythonProgram::importSymbol() adding const %s.%s = '%s'\n", module, symbol, // Py_TYPE(value)->tp_name); ValueHolder v(getQoreValue(xsink, value), xsink); if (*xsink) { return -1; } // skip empty values if (!v) { return 0; } const QoreTypeInfo* typeInfo = v->getFullTypeInfo(); QoreString ns_path = module_context; ns_path.replaceAll(".", "::"); QoreNamespace* ns = pyns->findCreateNamespacePathAll(ns_path.c_str()); if (ns->findLocalConstant(symbol)) { return 0; } //std::string path = ns->getPath(); //printd(5, "QorePythonProgram::importSymbol() adding const %s.%s = '%s' (ns: %s)\n", module_context, symbol, // qore_type_get_name(typeInfo), path.c_str()); ns->addConstant(symbol, v.release(), typeInfo); return 0; } // Python integration PyObject* QorePythonProgram::callQoreFunction(PyObject* self, PyObject* args) { /* QorePythonReferenceHolder selfstr(PyObject_Repr(self)); assert(PyUnicode_Check(*selfstr)); QorePythonReferenceHolder argstr(PyObject_Repr(args)); assert(PyUnicode_Check(*argstr)); printd(5, "QorePythonProgram::callQoreFunction() self: %p (%s) args: %s\n", self, PyUnicode_AsUTF8(*selfstr), PyUnicode_AsUTF8(*argstr)); */ assert(PyCapsule_CheckExact(self)); func_capsule_t* fc = reinterpret_cast(PyCapsule_GetPointer(self, nullptr)); assert(&fc->func); assert(fc->py_pgm); // get Qore arguments ExceptionSink xsink; assert(PyTuple_Check(args)); ReferenceHolder qargs(fc->py_pgm->getQoreListFromTuple(&xsink, args), &xsink); if (!xsink) { ValueHolder rv(&xsink); { QorePythonReleaseGilHelper prgh; rv = fc->func.evalFunction(nullptr, *qargs, fc->py_pgm->getQoreProgram(), &xsink); } if (!xsink) { QorePythonReferenceHolder py_rv(fc->py_pgm->getPythonValue(*rv, &xsink)); if (!xsink) { assert(py_rv); return py_rv.release(); } } } fc->py_pgm->raisePythonException(xsink); assert(PyErr_Occurred()); return nullptr; }