Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

950 load plugins in a separate namespace #953

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bindings/python/tests/python-mapping-plugins/plugin1.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import dlite


class Person2SimplePerson(DLiteMappingBase):
class Person2SimplePerson(dlite.DLiteMappingBase):
name = "Person2SimplePerson"
output_uri = "http://onto-ns.com/meta/0.1/SimplePerson"
input_uris = ["http://onto-ns.com/meta/0.1/Person"]
Expand All @@ -15,7 +15,7 @@ def map(self, instances):
return simple


class SimplePerson2Person(DLiteMappingBase):
class SimplePerson2Person(dlite.DLiteMappingBase):
name = "SimplePerson2Person"
output_uri = "http://onto-ns.com/meta/0.1/Person"
input_uris = ["http://onto-ns.com/meta/0.1/SimplePerson"]
Expand Down
8 changes: 4 additions & 4 deletions doc/user_guide/storage_plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,12 @@ A complete example can be found in the [Python storage plugin example].


:::{danger}
**When writing a Python storage plugin, do not define any variables or functions outside the `DLiteStorageBase` subclass!**

The reason for this requirement is that all plugins will be loaded into the same shared scope within the built-in interpreter.
Hence, variables or functions outside the plugin class may interfere with other plugins, resulting in confusing and hard-to-find bugs.
**For DLite <0.5.23 storage plugins were executed in the same scope.
Hence, to avoid confusing and hard-to-find bugs due to interference between your plugins, you should not define any variables or functions outside the `DLiteStorageBase` subclass!**
:::

Since DLite v0.5.23, plugins are evaluated in separate scopes (which are available in `dlite._plugindict).




Expand Down
5 changes: 4 additions & 1 deletion src/pyembed/dlite-pyembed-utils.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "dlite-macros.h"
#include "dlite-misc.h"
#include "dlite-pyembed.h"
#include "dlite-pyembed-utils.h"
Expand All @@ -8,6 +9,8 @@

A side effect of calling this function is that the module will
be imported if it is available.

Use PyImport_GetModule() if you don't want to import the module.
*/
int dlite_pyembed_has_module(const char *module_name)
{
Expand All @@ -16,7 +19,7 @@ int dlite_pyembed_has_module(const char *module_name)
dlite_pyembed_initialise();

if (!(name = PyUnicode_FromString(module_name)))
return dlite_err(1, "invalid string: '%s'", module_name), 0;
return dlite_err(dliteValueError, "invalid string: '%s'", module_name), 0;

PyErr_Fetch(&type, &value, &tb);
module = PyImport_Import(name);
Expand Down
4 changes: 4 additions & 0 deletions src/pyembed/dlite-pyembed-utils.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef _DLITE_PYEMBED_UTILS_H
#define _DLITE_PYEMBED_UTILS_H


/**
@file
@brief Utility functions for embedding Python
Expand All @@ -15,8 +16,11 @@

A side effect of calling this function is that the module will
be imported if it is available.

Use PyImport_GetModule() if you don't want to import the module.
*/
int dlite_pyembed_has_module(const char *module_name);



#endif /* _DLITE_PYEMBED_UTILS_H */
137 changes: 122 additions & 15 deletions src/pyembed/dlite-pyembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ typedef struct {
/* Global state for this module */
typedef struct {
ErrorCorrelation *errcorr; /* NULL-terminated array */
int initialised; /* Whether DLite pyembed has been initialised */
PyObject *dlitedict; /* Cached dlite dictionary */
} PyembedGlobals;


Expand Down Expand Up @@ -136,7 +138,11 @@ static const ErrorCorrelation *error_correlations(void)
*/
void dlite_pyembed_initialise(void)
{
if (!Py_IsInitialized() || !dlite_behavior_get("singleInterpreter")) {
PyembedGlobals *g = get_globals();

if (!g->initialised &&
(!Py_IsInitialized() || !dlite_behavior_get("singleInterpreter"))) {

/*
Python 3.8 and later implements the new Python
Initialisation Configuration.
Expand Down Expand Up @@ -207,6 +213,8 @@ void dlite_pyembed_initialise(void)
if (PyList_Insert(sys_path, 0, path))
FAIL1("cannot insert %s into sys.path", dlite_PYTHONPATH);
}

g->initialised = 1;
fail:
Py_XDECREF(sys);
Py_XDECREF(sys_path);
Expand Down Expand Up @@ -575,19 +583,19 @@ PyObject *dlite_pyembed_load_plugins(FUPaths *paths, PyObject *baseclass,
char ***failed_paths, size_t *failed_len)
{
const char *path;
PyObject *main_dict, *ppath=NULL, *pfun=NULL, *subclasses=NULL, *lst=NULL;
PyObject *ppath=NULL, *pfun=NULL, *subclasses=NULL, *lst=NULL;
PyObject *subclassnames=NULL;
FUIter *iter;
int i;
char errors[4098] = "";

dlite_errclr();
dlite_pyembed_initialise();
if (!(main_dict = dlite_python_maindict())) goto fail;

/* Get list of initial subclasses and corresponding set subclassnames */
if ((pfun = PyObject_GetAttrString(baseclass, "__subclasses__")))
subclasses = PyObject_CallFunctionObjArgs(pfun, NULL);

Py_XDECREF(pfun);
if (!(subclassnames = PySet_New(NULL))) FAIL("cannot create empty set");
for (i=0; i < PyList_Size(subclasses); i++) {
Expand All @@ -606,17 +614,23 @@ PyObject *dlite_pyembed_load_plugins(FUPaths *paths, PyObject *baseclass,
/* Load all modules in `paths` */
if (!(iter = fu_pathsiter_init(paths, "*.py"))) goto fail;
while ((path = fu_pathsiter_next(iter))) {
int stat;
FILE *fp=NULL;
char *basename=NULL;
char *stem;

if ((stem = fu_stem(path))) {
int stat;
FILE *fp=NULL;
PyObject *plugindict;

if (!(ppath = PyUnicode_FromString(path)))
FAIL1("cannot create Python string from path: '%s'", path);
stat = PyDict_SetItemString(main_dict, "__file__", ppath);
Py_DECREF(ppath);
if (stat) FAIL("cannot assign path to '__file__' in dict of main module");
if (!(plugindict = dlite_python_plugindict(stem))) goto fail;
//if (!(plugindict = dlite_python_dlitedict())) goto fail;

if (!(ppath = PyUnicode_FromString(path)))
FAIL1("cannot create Python string from path: '%s'", path);
stat = PyDict_SetItemString(plugindict, "__file__", ppath);
Py_DECREF(ppath);
if (stat)
FAIL("cannot assign path to '__file__' in dict of main module");

if ((basename = fu_basename(path))) {
size_t n;
char **q = (failed_paths) ? *failed_paths : NULL;
for (n=0; q && *q; n++)
Expand All @@ -625,8 +639,8 @@ PyObject *dlite_pyembed_load_plugins(FUPaths *paths, PyObject *baseclass,

if (!in_failed) {
if ((fp = fopen(path, "r"))) {
PyObject *ret = PyRun_File(fp, basename, Py_file_input, main_dict,
main_dict);
PyObject *ret = PyRun_File(fp, path, Py_file_input, plugindict,
plugindict);
if (!ret) {

if (failed_paths && failed_len) {
Expand All @@ -641,7 +655,7 @@ PyObject *dlite_pyembed_load_plugins(FUPaths *paths, PyObject *baseclass,
Py_XDECREF(ret);
}
}
free(basename);
free(stem);
}

}
Expand Down Expand Up @@ -680,3 +694,96 @@ PyObject *dlite_pyembed_load_plugins(FUPaths *paths, PyObject *baseclass,
Py_XDECREF(subclassnames);
return subclasses;
}


/*
Return borrowed reference to a dict object for DLite or NULL on error.

If the dlite module has been imported, the dlite module `__dict__`
is returned. Otherwise a warning is issued and a, possible newly
created, `__main__._dlite` dict is returned.

The returned reference is cashed and will therefore always be consistent.

Use dlite_python_module_dict() if you only want the dlite module dict.
*/
PyObject *dlite_python_dlitedict(void)
{
PyObject *name=NULL, *module=NULL, *dict=NULL;
PyembedGlobals *g = get_globals();

dlite_pyembed_initialise();

/* Caching the dlitedict */
if (g->dlitedict) return g->dlitedict;

if (!(name = PyUnicode_FromString("dlite")))
FAILCODE(dliteValueError, "invalid string: 'dlite'");

if (!(module = PyImport_GetModule(name))) {
PyObject *maindict = dlite_python_maindict();
if (!maindict) goto fail;
if (!(dict = PyDict_GetItemString(maindict, "_dlite"))) {
if (!(dict = PyDict_New()))
FAILCODE(dlitePythonError, "cannot create dict `__main__._dlite`");
int stat = PyDict_SetItemString(maindict, "_dlite", dict);
Py_DECREF(dict);
if (stat) FAILCODE(dlitePythonError,
"cannot insert dict `__main__._dlite`");
dlite_warnx("dlite not imported. Created dict `__main__._dlite`");
}
} else {
if (!(dict = PyModule_GetDict(module)))
FAILCODE(dlitePythonError, "cannot get dlite module dict");
}

g->dlitedict = dict;

fail:
Py_XDECREF(name);
Py_XDECREF(module);

return dict;
}


/*
Return borrowed reference to a dict serving as a namespace for the
given plugin.

The returned dict is accessable from Python as
`dlite._plugindict[plugin_name]`. The dict will be created if it
doesn't already exists.

Returns NULL on error.
*/
PyObject *dlite_python_plugindict(const char *plugin_name)
{
PyObject *dlitedict=NULL, *plugindict=NULL, *dict=NULL;

if (!(dlitedict = dlite_python_dlitedict())) goto fail;

if (!(plugindict = PyDict_GetItemString(dlitedict, "_plugindict"))) {
if (!(plugindict = PyDict_New()))
FAILCODE(dlitePythonError, "cannot create dict `dlite._plugindict`");
int stat = PyDict_SetItemString(dlitedict, "_plugindict", plugindict);
Py_DECREF(plugindict);
if (stat) FAILCODE(dlitePythonError,
"cannot insert dict `dlite._plugindict`");
}

if (!(dict = PyDict_GetItemString(plugindict, plugin_name))) {
if (!(dict = PyDict_New()))
FAILCODE1(dlitePythonError,
"cannot create dict `dlite._plugindict[%s]`",
plugin_name);
int stat = PyDict_SetItemString(plugindict, plugin_name, dict);
Py_DECREF(dict);
if (stat) FAILCODE1(dlitePythonError,
"cannot insert dict `dlite._plugindict[%s]`",
plugin_name);
}

fail:
return dict;
}
20 changes: 20 additions & 0 deletions src/pyembed/dlite-pyembed.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,24 @@ PyObject *dlite_pyembed_load_plugins(FUPaths *paths, PyObject *baseclass,
char ***failed_paths, size_t *failed_len);


/**
Return borrowed reference to the `__dict__` object in the dlite
module or NULL on error.
*/
PyObject *dlite_python_dlitedict(void);


/**
Return borrowed reference to a dict serving as a namespace for the
given plugin.

The returned dict is accessable from Python as
`dlite._plugindict[plugin_name]`. The dict will be created if it
doesn't already exists.

Returns NULL on error.
*/
PyObject *dlite_python_plugindict(const char *plugin_name);


#endif /* _DLITE_PYEMBED_H */
29 changes: 14 additions & 15 deletions src/pyembed/dlite-python-singletons.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@


/*
Returns a pointer to __main__.__dict__ of the embedded interpreater
or NULL on error.
Returns a borrowed reference to `__main__.__dict__` or NULL on error.
*/
PyObject *dlite_python_maindict(void)
{
Expand All @@ -23,31 +22,31 @@ PyObject *dlite_python_maindict(void)


/*
Creates a new empty singleton class and return it.
Creates a new empty singleton class in the dlite module and return it.

The name of the new class is `classname`.

Returns NULL on error.
*/
PyObject *dlite_python_mainclass(const char *classname)
PyObject *dlite_python_dliteclass(const char *classname)
{
PyObject *main_dict, *class;
PyObject *dlitedict, *class, *result=NULL;
char initcode[96];

if (!(main_dict = dlite_python_maindict())) goto fail;
if (!(dlitedict = dlite_python_dlitedict())) goto fail;

/* Return newclass if it is already in __main__.__dict__ */
if ((class = PyDict_GetItemString(main_dict, classname)))
if ((class = PyDict_GetItemString(dlitedict, classname)))
return class;

/* Create and inject new class into the __main__ module */
if (snprintf(initcode, sizeof(initcode), "class %s: pass\n", classname) < 0)
FAIL("failure to create initialisation code for embedded Python "
"__main__ module");
if (PyRun_SimpleString(initcode))
FAIL("failure when running embedded Python __main__ module "
"initialisation code");
if ((class = PyDict_GetItemString(main_dict, classname)))
FAILCODE1(dliteSystemError, "cannot create init code for class '%s'",
classname);
if (!(result = PyRun_String(initcode, Py_single_input, dlitedict, dlitedict)))
FAILCODE1(dlitePythonError, "failure running Python code '%s'", initcode);
Py_DECREF(result);
if ((class = PyDict_GetItemString(dlitedict, classname)))
return class;

fail:
Expand All @@ -58,14 +57,14 @@ PyObject *dlite_python_mainclass(const char *classname)
/* Returns the base class for storage plugins. */
PyObject *dlite_python_storage_base(void)
{
return dlite_python_mainclass("DLiteStorageBase");
return dlite_python_dliteclass("DLiteStorageBase");
}


/* Returns the base class for mapping plugins. */
PyObject *dlite_python_mapping_base(void)
{
return dlite_python_mainclass("DLiteMappingBase");
return dlite_python_dliteclass("DLiteMappingBase");
}


Expand Down
7 changes: 3 additions & 4 deletions src/pyembed/dlite-python-singletons.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,18 @@


/**
Returns a pointer to __main__.__dict__ of the embedded interpreater
or NULL on error.
Returns a borrowed reference to `__main__.__dict__` or NULL on error.
*/
PyObject *dlite_python_maindict(void);

/**
Creates a new empty singleton class and return it.
Creates a new empty singleton class in the dlite module and return it.

The name of the new class is `classname`.

Returns NULL on error.
*/
PyObject *dlite_python_mainclass(const char *classname);
PyObject *dlite_python_dliteclass(const char *classname);


/**
Expand Down
Loading