/* 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 "QoreLoader.h"
#include
#include "QoreMetaPathFinder.h"
#include "QorePythonProgram.h"
#include "PythonQoreClass.h"
#include
QorePythonManualReferenceHolder QoreLoader::loader_cls;
QorePythonManualReferenceHolder QoreLoader::loader;
static bool moduleNameMatches(const char* item_mod, const char* name) {
return item_mod && !strcmp(item_mod, name);
}
static bool namespaceHasDirectItemFromModule(const QoreNamespace& ns, const char* name) {
QoreNamespaceFunctionIterator fi(ns);
while (fi.next()) {
if (moduleNameMatches(fi.get().getModuleName(), name)) {
return true;
}
}
QoreNamespaceConstantIterator ci(ns);
while (ci.next()) {
if (moduleNameMatches(ci.get().getModuleName(), name)) {
return true;
}
}
QoreNamespaceClassIterator class_i(ns);
while (class_i.next()) {
if (moduleNameMatches(class_i.get().getModuleName(), name)) {
return true;
}
}
QoreNamespaceTypedHashIterator hash_i(ns);
while (hash_i.next()) {
if (moduleNameMatches(hash_i.get().getModuleName(), name)) {
return true;
}
}
QoreNamespaceEnumIterator enum_i(ns);
while (enum_i.next()) {
if (moduleNameMatches(enum_i.get().getModuleName(), name)) {
return true;
}
}
return false;
}
static PyMethodDef QoreLoader_methods[] = {
{"create_module", QoreLoader::create_module, METH_VARARGS, "QoreLoader.create_module() implementation"},
{"exec_module", QoreLoader::exec_module, METH_VARARGS, "QoreLoader.exec_module() implementation"},
{nullptr, nullptr},
};
PyDoc_STRVAR(QoreLoader_doc,
"QoreLoader()\n\
\n\
Python modules for Qore code.");
PyTypeObject QoreLoader_Type = {
PyVarObject_HEAD_INIT(nullptr, 0)
"QoreLoader", // tp_name
0, // tp_basicsize
0, // tp_itemsize
// Slots
(destructor)QoreLoader::dealloc, // tp_dealloc
0, // tp_vectorcall_offset
0, // tp_getattr
0, // tp_setattr
0, // tp_as_async
QoreLoader::repr, // tp_repr
0, // tp_as_number
0, // tp_as_sequence
0, // tp_as_mapping
0, // tp_hash
0, // tp_call
0, // tp_str
PyObject_GenericGetAttr, // tp_getattro
0, // tp_setattro
0, // tp_as_buffer
Py_TPFLAGS_DEFAULT, // tp_flags
QoreLoader_doc, // tp_doc
0, // tp_traverse
0, // tp_clear
0, // tp_richcompare
0, // tp_weaklistoffset
0, // tp_iter
0, // tp_iternext
QoreLoader_methods, // tp_methods
0, // tp_members
0, // tp_getset
&PyBaseObject_Type, // tp_base
0, // tp_dict
0, // tp_descr_get
0, // tp_descr_set
0, // tp_dictoffset
0, // tp_init
PyType_GenericAlloc, // tp_alloc
PyType_GenericNew, // tp_new
PyObject_Del, // tp_free
};
int QoreLoader::init() {
if (PyType_Ready(&QoreLoader_Type) < 0) {
printd(5, "QoreLoader::init() type initialization failed\n");
return -1;
}
QorePythonReferenceHolder args(PyTuple_New(0));
loader = PyObject_CallObject((PyObject*)&QoreLoader_Type, *args);
//loader = PyObject_CallObject((PyObject*)*loader_cls, *args);
if (PyType_Ready(&PythonQoreException_Type) < 0) {
return -1;
}
return 0;
}
void QoreLoader::del() {
loader.purge();
loader_cls.purge();
}
PyObject* QoreLoader::getLoaderRef() {
assert(*loader);
Py_INCREF(*loader);
return *loader;
}
PyObject* QoreLoader::getLoader() {
assert(*loader);
return *loader;
}
void QoreLoader::dealloc(PyObject* self) {
//PyObject_GC_UnTrack(self);
Py_TYPE(self)->tp_free(self);
}
PyObject* QoreLoader::repr(PyObject* obj) {
QoreStringMaker str("QoreLoader object %p", obj);
return PyUnicode_FromStringAndSize(str.c_str(), str.size());
}
// class method functions
PyObject* QoreLoader::create_module(PyObject* self, PyObject* args) {
Py_INCREF(Py_None);
return Py_None;
}
PyObject* QoreLoader::exec_module(PyObject* self, PyObject* args) {
assert(PyTuple_Check(args));
#ifdef _QORE_PYTHON_DEBUG_EXEC_MODULE_ARGS
QorePythonReferenceHolder argstr(PyObject_Repr(args));
assert(PyUnicode_Check(*argstr));
printd(5, "QoreLoader::exec_module() self: %p args: %s\n", self, PyUnicode_AsUTF8(*argstr));
#endif
// get module
// returns a borrowed reference
PyObject* mod = PyTuple_GetItem(args, 0);
assert(PyModule_Check(mod));
// get module name
assert(PyObject_HasAttrString(mod, "__name__"));
QorePythonReferenceHolder name(PyObject_GetAttrString(mod, "__name__"));
assert(PyUnicode_Check(*name));
const char* orig_name_str = PyUnicode_AsUTF8(*name);
const char* name_str = orig_name_str;
printd(5, "QoreLoader::exec_module() mod: '%s'\n", name_str);
QorePythonProgram* qore_python_pgm = QorePythonProgram::getContext();
QoreProgram* mod_pgm = qore_python_pgm->getQoreProgram();
printd(5, "QoreLoader::exec_module() qore_python_pgm: %p mod pgm: %p\n", qore_python_pgm, mod_pgm);
// get the root namespace(s) backing the Python module; a Qore module can declare items in more than one root
// namespace, in which case all of them have to be imported, otherwise the items the module declares in the
// namespaces that are left out are not visible in Python at all
nsvec_t nsvec;
if (!strcmp(name_str, "qore")) {
nsvec.push_back(mod_pgm->getQoreNS());
} else {
assert(!strncmp(name_str, "qore.", 5));
name_str += 5;
if (!strchr(name_str, '.')) {
if (!strcmp(name_str, "__root__")) {
nsvec.push_back(mod_pgm->getRootNS());
} else {
getModuleRootNsList(name_str, mod_pgm, nsvec);
}
}
}
if (!nsvec.empty()) {
QoreProgramContextHelper pch(mod_pgm);
for (const QoreNamespace* ns : nsvec) {
printd(5, "QoreLoader::exec_module() found '%s' NS %p: '::%s'\n", name_str, ns, ns->getName());
qore_python_pgm->importQoreToPython(mod, *ns, name_str);
if (PyErr_Occurred()) {
return nullptr;
}
}
// the primary namespace gives the module its path
std::string nspath = nsvec[0]->getPath();
QorePythonReferenceHolder py_path(PyUnicode_FromStringAndSize(nspath.c_str(), nspath.size()));
if (PyObject_SetAttrString(mod, "__path__", *py_path)) {
return nullptr;
}
} else {
QoreStringMaker desc("cannot find Qore namespace for Python module '%s'", orig_name_str);
PyErr_SetString(PyExc_NameError, desc.c_str());
return nullptr;
}
Py_INCREF(Py_None);
return Py_None;
}
void QoreLoader::getModuleRootNsList(const char* name, QoreProgram* mod_pgm, nsvec_t& nsvec) {
ReferenceHolder all_mod_info(MM.getModuleHash(), nullptr);
mod_dep_map_t mod_dep_map;
// otherwise look for a public namespace and then find the ealiest ancestor provided by the module
const QoreNamespace* root_ns = mod_pgm->getRootNS();
getModuleRootNsListIntern(name, *root_ns, *all_mod_info, mod_dep_map, true, nsvec);
if (nsvec.empty()) {
getModuleRootNsListIntern(name, *root_ns, *all_mod_info, mod_dep_map, false, nsvec);
}
}
// Returns true when the namespace is directly owned by the named module â either it was added
// as a contributor (isFromModule) or it holds at least one direct item declared by the module.
static bool namespaceMatchesModuleDirect(const QoreNamespace* parent, const char* name) {
if (!parent || parent->isRoot()) {
return false;
}
return parent->isFromModule(name) || namespaceHasDirectItemFromModule(*parent, name);
}
// Returns true when the namespace is matched to the named module *only* via the module's
// reexport list â i.e. one of the module's reexported dependencies owns the namespace. Kept
// separate from the direct check so callers can prefer direct-ownership candidates and avoid
// rooting an importing module's Python package at a reexported dependency's namespace.
static bool namespaceMatchesModuleViaReexport(const QoreNamespace* parent, const char* name,
const QoreHashNode* all_mod_info, mod_dep_map_t& mod_dep_map) {
if (!parent || parent->isRoot() || !all_mod_info) {
return false;
}
const char* mod = parent->getModuleName();
const QoreListNode* reexport_list = nullptr;
// see if we have the reexport list already
mod_dep_map_t::iterator i;
if (!mod || (i = mod_dep_map.lower_bound(mod)) == mod_dep_map.end() || strcmp(i->first, mod)) {
const QoreHashNode* mod_info = all_mod_info->getKeyValue(name).get();
if (!mod_info) {
return false;
}
reexport_list = mod_info->getKeyValue("reexported-modules").get();
if (!reexport_list) {
return false;
}
if (mod) {
mod_dep_map.insert(i, mod_dep_map_t::value_type(mod, reexport_list));
}
} else {
reexport_list = i->second;
}
ConstListIterator li(reexport_list);
while (li.next()) {
const QoreValue v = li.getValue();
if (v.getType() == NT_STRING) {
QoreStringValueHelper str(v);
if (parent->isFromModule(str->c_str()) || namespaceHasDirectItemFromModule(*parent, str->c_str())) {
return true;
}
}
}
return false;
}
void QoreLoader::getModuleRootNsListIntern(const char* name, const QoreNamespace& root_ns,
const QoreHashNode* all_mod_info, mod_dep_map_t& mod_dep_map, bool check_mod, nsvec_t& nsvec) {
// A Qore module can declare items in any number of root namespaces (for example QorusClientCore declares
// classes in both ::OMQ and ::RemoteDevelopment), so every namespace that directly owns items declared by
// the module is collected; returning only one of them would leave everything the module declares in the
// others invisible to Python, and which one was returned depended on namespace iteration order.
//
// Namespaces matched only through the module's reexport list are a fallback: a namespace that owns items
// declared by `name` is always a better root than a namespace that merely belongs to a module reexported by
// `name` â otherwise a Python "from qore.X import Y" for a module X that reexports dependency D would pick
// D's namespace as X's root and hide X's own items (e.g. QorusClientBase reexports HttpClientIo, so
// ::HttpClientIo would wrongly displace ::OMQ as the root for "qore.QorusClientBase").
const QoreNamespace* exact_candidate = nullptr;
const QoreNamespace* reexport_candidate = nullptr;
QoreNamespaceConstIterator i(root_ns);
while (i.next()) {
const QoreNamespace* ns = &i.get();
if (!check_mod) {
if (!strcmp(ns->getName(), name)) {
nsvec.push_back(ns);
return;
}
continue;
}
if (!isModule(ns, name, all_mod_info, mod_dep_map)) {
continue;
}
printd(5, "QoreLoader::getModuleRootNsList('%s') found '%s' (%p)\n", name, ns->getPath().c_str(), ns);
// try to find parent ns
while (true) {
const QoreNamespace* parent = ns->getParent();
if (!isModule(parent, name, all_mod_info, mod_dep_map)) {
printd(5, "QoreLoader::getModuleRootNsList('%s') invalid parent '%s'\n", name,
parent->getPath().c_str());
break;
}
ns = parent;
printd(5, "QoreLoader::getModuleRootNsList('%s') got parent '%s'\n", name, ns->getPath().c_str());
}
if (!namespaceMatchesModuleDirect(ns, name)) {
if (!reexport_candidate) {
reexport_candidate = ns;
}
continue;
}
// the parent walk above can map several namespaces to the same ancestor
if (std::find(nsvec.begin(), nsvec.end(), ns) != nsvec.end()) {
continue;
}
printd(5, "QoreLoader::getModuleRootNsList('%s') adding direct namespace '%s'\n", name,
ns->getPath().c_str());
nsvec.push_back(ns);
// a namespace with the module's own name is the module's primary namespace
if (!exact_candidate && !strcmp(ns->getName(), name)) {
exact_candidate = ns;
}
}
if (nsvec.empty()) {
if (reexport_candidate) {
printd(5, "QoreLoader::getModuleRootNsList('%s') returning reexport candidate '%s'\n", name,
reexport_candidate->getPath().c_str());
nsvec.push_back(reexport_candidate);
}
return;
}
// the module's primary namespace gives the Python module its path, so it must come first
if (exact_candidate && nsvec[0] != exact_candidate) {
nsvec.erase(std::find(nsvec.begin(), nsvec.end(), exact_candidate));
nsvec.insert(nsvec.begin(), exact_candidate);
}
}
bool QoreLoader::isModule(const QoreNamespace* parent, const char* name, const QoreHashNode* all_mod_info,
mod_dep_map_t& mod_dep_map) {
return namespaceMatchesModuleDirect(parent, name)
|| namespaceMatchesModuleViaReexport(parent, name, all_mod_info, mod_dep_map);
}