|
@@ -29,6 +29,7 @@
|
|
#include "rtlfield_imp.hpp"
|
|
#include "rtlfield_imp.hpp"
|
|
#include "nbcd.hpp"
|
|
#include "nbcd.hpp"
|
|
#include "roxiemem.hpp"
|
|
#include "roxiemem.hpp"
|
|
|
|
+#include "enginecontext.hpp"
|
|
|
|
|
|
static const char * compatibleVersions[] = {
|
|
static const char * compatibleVersions[] = {
|
|
"Python2.7 Embed Helper 1.0.0",
|
|
"Python2.7 Embed Helper 1.0.0",
|
|
@@ -98,6 +99,19 @@ public:
|
|
inline X **ref() { return &ptr; }
|
|
inline X **ref() { return &ptr; }
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+
|
|
|
|
+__declspec(noreturn) static void failx(const char *msg, ...) __attribute__((format(printf, 1, 2), noreturn));
|
|
|
|
+
|
|
|
|
+static void failx(const char *message, ...)
|
|
|
|
+{
|
|
|
|
+ va_list args;
|
|
|
|
+ va_start(args,message);
|
|
|
|
+ StringBuffer msg;
|
|
|
|
+ msg.append("pyembed: ").valist_appendf(message,args);
|
|
|
|
+ va_end(args);
|
|
|
|
+ rtlFail(0, msg.str());
|
|
|
|
+}
|
|
|
|
+
|
|
// call checkPythonError to throw an exception if Python error state is set
|
|
// call checkPythonError to throw an exception if Python error state is set
|
|
|
|
|
|
static void checkPythonError()
|
|
static void checkPythonError()
|
|
@@ -109,8 +123,7 @@ static void checkPythonError()
|
|
PyErr_Fetch(pType.ref(), pValue.ref(), pTraceBack.ref());
|
|
PyErr_Fetch(pType.ref(), pValue.ref(), pTraceBack.ref());
|
|
OwnedPyObject valStr = PyObject_Str(pValue);
|
|
OwnedPyObject valStr = PyObject_Str(pValue);
|
|
PyErr_Clear();
|
|
PyErr_Clear();
|
|
- VStringBuffer errMessage("pyembed: %s", PyString_AsString(valStr));
|
|
|
|
- rtlFail(0, errMessage.str());
|
|
|
|
|
|
+ failx("pyembed: %s", PyString_AsString(valStr));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -271,6 +284,7 @@ public:
|
|
const char *argv[] = { nullptr };
|
|
const char *argv[] = { nullptr };
|
|
PySys_SetArgvEx(0, (char **) argv, 0);
|
|
PySys_SetArgvEx(0, (char **) argv, 0);
|
|
PyEval_InitThreads();
|
|
PyEval_InitThreads();
|
|
|
|
+ preservedScopes.setown(PyDict_New());
|
|
tstate = PyEval_SaveThread();
|
|
tstate = PyEval_SaveThread();
|
|
initialized = true;
|
|
initialized = true;
|
|
}
|
|
}
|
|
@@ -286,6 +300,7 @@ public:
|
|
namedtuple.clear();
|
|
namedtuple.clear();
|
|
namedtupleTypes.clear();
|
|
namedtupleTypes.clear();
|
|
compiledScripts.clear();
|
|
compiledScripts.clear();
|
|
|
|
+ preservedScopes.clear();
|
|
Py_Finalize();
|
|
Py_Finalize();
|
|
}
|
|
}
|
|
if (pythonLibrary)
|
|
if (pythonLibrary)
|
|
@@ -401,6 +416,31 @@ public:
|
|
}
|
|
}
|
|
return code.getClear();
|
|
return code.getClear();
|
|
}
|
|
}
|
|
|
|
+ PyObject *getNamedScope(const char *key, bool &isNew)
|
|
|
|
+ {
|
|
|
|
+ if (!preservedScopes)
|
|
|
|
+ preservedScopes.setown(PyDict_New());
|
|
|
|
+ OwnedPyObject scope;
|
|
|
|
+ scope.set(PyDict_GetItemString(preservedScopes, key));
|
|
|
|
+ if (!scope)
|
|
|
|
+ {
|
|
|
|
+ scope.setown(PyDict_New());
|
|
|
|
+ PyDict_SetItemString(preservedScopes, key, scope);
|
|
|
|
+ isNew = true;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ isNew = false;
|
|
|
|
+ return scope.getClear();
|
|
|
|
+ }
|
|
|
|
+ void releaseNamedScope(const char *key)
|
|
|
|
+ {
|
|
|
|
+ if (preservedScopes)
|
|
|
|
+ {
|
|
|
|
+ PyDict_DelItemString(preservedScopes, key);
|
|
|
|
+ PyErr_Clear(); // Should be present, but ignore the error if it is not
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ static void unregister(const char *key);
|
|
protected:
|
|
protected:
|
|
static StringBuffer &wrapPythonText(StringBuffer &out, const char *in, const char *params)
|
|
static StringBuffer &wrapPythonText(StringBuffer &out, const char *in, const char *params)
|
|
{
|
|
{
|
|
@@ -421,6 +461,7 @@ protected:
|
|
OwnedPyObject namedtuple; // collections.namedtuple
|
|
OwnedPyObject namedtuple; // collections.namedtuple
|
|
OwnedPyObject namedtupleTypes; // dictionary of return values from namedtuple()
|
|
OwnedPyObject namedtupleTypes; // dictionary of return values from namedtuple()
|
|
OwnedPyObject compiledScripts; // dictionary of previously compiled scripts
|
|
OwnedPyObject compiledScripts; // dictionary of previously compiled scripts
|
|
|
|
+ OwnedPyObject preservedScopes; // dictionary of preserved scopes
|
|
} globalState;
|
|
} globalState;
|
|
|
|
|
|
MODULE_INIT(INIT_PRIORITY_STANDARD)
|
|
MODULE_INIT(INIT_PRIORITY_STANDARD)
|
|
@@ -1193,6 +1234,13 @@ private:
|
|
PyThreadState * &state;
|
|
PyThreadState * &state;
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+void Python27GlobalState::unregister(const char *key)
|
|
|
|
+{
|
|
|
|
+ checkThreadContext();
|
|
|
|
+ GILBlock b(threadContext->threadState);
|
|
|
|
+ globalState.releaseNamedScope(key);
|
|
|
|
+}
|
|
|
|
+
|
|
// A Python function that returns a dataset will return a PythonRowStream object that can be
|
|
// A Python function that returns a dataset will return a PythonRowStream object that can be
|
|
// interrogated to return each row of the result in turn
|
|
// interrogated to return each row of the result in turn
|
|
|
|
|
|
@@ -1255,9 +1303,71 @@ public:
|
|
: sharedCtx(_sharedCtx)
|
|
: sharedCtx(_sharedCtx)
|
|
{
|
|
{
|
|
PyEval_RestoreThread(sharedCtx->threadState);
|
|
PyEval_RestoreThread(sharedCtx->threadState);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void setScopes(ICodeContext *codeCtx, const char *_options)
|
|
|
|
+ {
|
|
locals.setown(PyDict_New());
|
|
locals.setown(PyDict_New());
|
|
- globals.setown(PyDict_New());
|
|
|
|
- PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins()); // required for import to work
|
|
|
|
|
|
+ StringArray options;
|
|
|
|
+ options.appendList(_options, ",");
|
|
|
|
+ StringBuffer scopeKey;
|
|
|
|
+ const char *scopeKey2 = nullptr;
|
|
|
|
+ bool registerCallback = false;
|
|
|
|
+ bool wuidScope = false;
|
|
|
|
+ IEngineContext *engine = nullptr;
|
|
|
|
+ ForEachItemIn(idx, options)
|
|
|
|
+ {
|
|
|
|
+ const char *opt = options.item(idx);
|
|
|
|
+ const char *val = strchr(opt, '=');
|
|
|
|
+ if (val)
|
|
|
|
+ {
|
|
|
|
+ StringBuffer optName(val-opt, opt);
|
|
|
|
+ val++;
|
|
|
|
+ if (strieq(optName, "globalscope"))
|
|
|
|
+ scopeKey2 = val;
|
|
|
|
+ else if (strieq(optName, "persist"))
|
|
|
|
+ {
|
|
|
|
+ if (scopeKey.length())
|
|
|
|
+ failx("persist option specified more than once");
|
|
|
|
+ if (strieq(val, "global"))
|
|
|
|
+ scopeKey.append("global");
|
|
|
|
+ else if (strieq(val, "workunit"))
|
|
|
|
+ {
|
|
|
|
+ engine = codeCtx->queryEngineContext();
|
|
|
|
+ wuidScope = true;
|
|
|
|
+ if (!engine)
|
|
|
|
+ failx("Persist mode 'workunit' not supported here");
|
|
|
|
+ }
|
|
|
|
+ else if (strieq(val, "query"))
|
|
|
|
+ {
|
|
|
|
+ engine = codeCtx->queryEngineContext();
|
|
|
|
+ wuidScope = false;
|
|
|
|
+ if (!engine)
|
|
|
|
+ failx("Persist mode 'query' not supported here");
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ failx("Unrecognized persist mode %s", val);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ failx("Unrecognized option %s", optName.str());
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ failx("Unrecognized option %s", opt);
|
|
|
|
+ }
|
|
|
|
+ if (engine)
|
|
|
|
+ engine->getQueryId(scopeKey, wuidScope);
|
|
|
|
+ if (scopeKey2)
|
|
|
|
+ scopeKey.append(':').append(scopeKey2);
|
|
|
|
+ if (scopeKey.length())
|
|
|
|
+ {
|
|
|
|
+ bool isNew;
|
|
|
|
+ globals.setown(globalState.getNamedScope(scopeKey, isNew));
|
|
|
|
+ if (isNew && engine)
|
|
|
|
+ engine->onTermination(Python27GlobalState::unregister, scopeKey.str(), wuidScope);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ globals.setown(PyDict_New());
|
|
|
|
+ PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins()); // required for import to work
|
|
}
|
|
}
|
|
~Python27EmbedContextBase()
|
|
~Python27EmbedContextBase()
|
|
{
|
|
{
|
|
@@ -1491,7 +1601,7 @@ protected:
|
|
class Python27EmbedScriptContext : public Python27EmbedContextBase
|
|
class Python27EmbedScriptContext : public Python27EmbedContextBase
|
|
{
|
|
{
|
|
public:
|
|
public:
|
|
- Python27EmbedScriptContext(PythonThreadContext *_sharedCtx, const char *options)
|
|
|
|
|
|
+ Python27EmbedScriptContext(PythonThreadContext *_sharedCtx)
|
|
: Python27EmbedContextBase(_sharedCtx)
|
|
: Python27EmbedContextBase(_sharedCtx)
|
|
{
|
|
{
|
|
}
|
|
}
|
|
@@ -1509,7 +1619,6 @@ public:
|
|
{
|
|
{
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
virtual void importFunction(size32_t lenChars, const char *text)
|
|
virtual void importFunction(size32_t lenChars, const char *text)
|
|
{
|
|
{
|
|
throwUnexpected();
|
|
throwUnexpected();
|
|
@@ -1549,7 +1658,7 @@ protected:
|
|
class Python27EmbedImportContext : public Python27EmbedContextBase
|
|
class Python27EmbedImportContext : public Python27EmbedContextBase
|
|
{
|
|
{
|
|
public:
|
|
public:
|
|
- Python27EmbedImportContext(PythonThreadContext *_sharedCtx, const char *options)
|
|
|
|
|
|
+ Python27EmbedImportContext(PythonThreadContext *_sharedCtx)
|
|
: Python27EmbedContextBase(_sharedCtx)
|
|
: Python27EmbedContextBase(_sharedCtx)
|
|
{
|
|
{
|
|
argcount = 0;
|
|
argcount = 0;
|
|
@@ -1605,10 +1714,13 @@ public:
|
|
virtual IEmbedFunctionContext *createFunctionContextEx(ICodeContext * ctx, unsigned flags, const char *options)
|
|
virtual IEmbedFunctionContext *createFunctionContextEx(ICodeContext * ctx, unsigned flags, const char *options)
|
|
{
|
|
{
|
|
checkThreadContext();
|
|
checkThreadContext();
|
|
|
|
+ Owned<Python27EmbedContextBase> ret;
|
|
if (flags & EFimport)
|
|
if (flags & EFimport)
|
|
- return new Python27EmbedImportContext(threadContext, options);
|
|
|
|
|
|
+ ret.setown(new Python27EmbedImportContext(threadContext));
|
|
else
|
|
else
|
|
- return new Python27EmbedScriptContext(threadContext, options);
|
|
|
|
|
|
+ ret.setown(new Python27EmbedScriptContext(threadContext));
|
|
|
|
+ ret->setScopes(ctx, options);
|
|
|
|
+ return ret.getClear();
|
|
}
|
|
}
|
|
virtual IEmbedServiceContext *createServiceContext(const char *service, unsigned flags, const char *options)
|
|
virtual IEmbedServiceContext *createServiceContext(const char *service, unsigned flags, const char *options)
|
|
{
|
|
{
|