1 #ifndef _PYTHON_PLUGIN_BASE_INTERFACE_H
2 #define _PYTHON_PLUGIN_BASE_INTERFACE_H
14 #include <plugin_manager.h>
16 #define SHIM_SCRIPT_REL_PATH "/python/fledge/plugins/common/shim/"
17 #define SHIM_SCRIPT_POSTFIX "_shim"
34 PyThreadState* state) :
50 void setCategoryName(
string category)
52 m_categoryName = category;
55 string& getCategoryName()
57 return m_categoryName;
65 PyThreadState* m_tState;
66 string m_categoryName;
72 static map<string, PythonModule*> *pythonModules =
new map<string, PythonModule*>();
74 static map<PLUGIN_HANDLE, PythonModule*> *pythonHandles =
new map<PLUGIN_HANDLE, PythonModule*>();
80 static string gPluginName;
85 static void plugin_reconfigure_fn(PLUGIN_HANDLE*,
const std::string&);
86 static void plugin_shutdown_fn(PLUGIN_HANDLE);
88 static void logErrorMessage();
89 static bool numpyImportError =
false;
98 void PluginInterfaceCleanup(
const string& pluginName)
100 bool removePython =
false;
105 "in PluginInterfaceCleanup, plugin '%s'",
112 PyGILState_STATE state = PyGILState_Ensure();
115 auto it = pythonModules->find(pluginName);
116 if (it != pythonModules->end())
119 removePython = it->second->m_init;
122 pythonModules->erase(it);
126 for (
auto h = pythonHandles->begin();
127 h != pythonHandles->end(); )
130 if (h->second->m_name.compare(pluginName) == 0)
133 if (h->second->m_module)
135 Py_CLEAR(h->second->m_module);
136 h->second->m_module = NULL;
144 h = pythonHandles->erase(h);
154 it->second->m_module)
156 Py_CLEAR(it->second->m_module);
157 it->second->m_module = NULL;
161 if (pythonModules->size() == 0)
164 delete pythonModules;
166 if (pythonHandles->size() == 0)
169 delete pythonHandles;
175 "started by plugin '%s'",
183 PyGILState_Release(state);
187 "called for plugin '%s'",
195 static void* PluginInterfaceGetInfo()
197 return (
void *) plugin_info_fn;
206 void set_loglevel_in_python_module(PyObject *python_module,
string s)
209 for (
auto & c: _loglevel) c = toupper(c);
210 const char *loglevel = _loglevel.c_str();
212 PyObject* mod = python_module;
215 PyObject* loggerObj = PyObject_GetAttrString(mod,
"_LOGGER");
216 if (loggerObj != NULL)
218 PyObject* method = PyObject_GetAttrString(loggerObj,
"setLevel");
221 PyObject *args = PyTuple_New(1);
222 PyObject *pValue = Py_BuildValue(
"s", loglevel);
223 PyTuple_SetItem(args, 0, pValue);
224 PyObject* retVal = PyObject_Call(method, args, NULL);
231 Logger::getLogger()->
debug(
"%s: %s: _LOGGER.setLevel(%s) done successfully", __FUNCTION__, s.c_str(), loglevel);
236 if (PyErr_Occurred())
263 const char *json_dumps(PyObject *json_dict)
266 PyObject *mod, *method;
268 PyGILState_STATE state = PyGILState_Ensure();
269 if ((mod = PyImport_ImportModule(
"json")) != NULL)
271 if ((method = PyObject_GetAttrString(mod,
"dumps")) != NULL)
273 PyObject *args = PyTuple_New(1);
274 PyObject *pValue = Py_BuildValue(
"O", json_dict);
275 PyTuple_SetItem(args, 0, pValue);
277 rval = PyObject_Call(method, args, NULL);
284 if (PyErr_Occurred())
310 PyGILState_Release(state);
312 const char *retVal = PyUnicode_AsUTF8(rval);
322 PyObject *json_loads(
const char *json_str)
325 PyObject *mod, *method;
327 PyGILState_STATE state = PyGILState_Ensure();
328 if ((mod = PyImport_ImportModule(
"json")) != NULL)
330 if ((method = PyObject_GetAttrString(mod,
"loads")) != NULL)
332 PyObject *args = PyTuple_New(1);
333 PyObject *pValue = Py_BuildValue(
"s", json_str);
334 PyTuple_SetItem(args, 0, pValue);
336 Logger::getLogger()->
debug(
"%s:%d: method=%p, args=%p, pValue=%p", __FUNCTION__, __LINE__, method, args, pValue);
337 rval = PyObject_Call(method, args, NULL);
344 if (PyErr_Occurred())
368 PyGILState_Release(state);
388 PyObject *dKey, *dValue;
391 PyObject* objectsRepresentation = PyObject_Repr(pyRetVal);
392 const char* s = PyUnicode_AsUTF8(objectsRepresentation);
394 Py_CLEAR(objectsRepresentation);
397 while (PyDict_Next(pyRetVal, &dPos, &dKey, &dValue))
399 const char* ckey = PyUnicode_AsUTF8(dKey);
400 const char* cval = PyUnicode_AsUTF8(dValue);
401 Logger::getLogger()->
debug(
"%s:%d, key=%s, value=%s, dValue type=%s", __FUNCTION__, __LINE__, ckey, cval, (Py_TYPE(dValue))->tp_name);
404 if (!PyDict_Check(dValue))
406 valStr =
new char [string(cval).length()+1];
407 std::strcpy (valStr, cval);
408 Logger::getLogger()->
debug(
"%s:%d, key=%s, value=%s, valStr=%s", __FUNCTION__, __LINE__, ckey, cval, valStr);
411 if(!strcmp(ckey,
"name"))
415 else if(!strcmp(ckey,
"version"))
419 else if(!strcmp(ckey,
"mode"))
422 stringstream ss(valStr);
428 while(getline(ss, s,
'|'))
431 if (s.compare(
"async")==0)
435 else if (s.compare(
"control")==0)
439 else if (s.compare(
"poll")==0)
443 else if (s.compare(
"none")==0)
453 else if(!strcmp(ckey,
"type"))
457 else if(!strcmp(ckey,
"interface"))
461 else if(!strcmp(ckey,
"config"))
464 if (strcmp((Py_TYPE(dValue))->tp_name,
"dict")==0)
466 PyObject* objectsRepresentation = PyObject_Repr(dValue);
467 const char* s = PyUnicode_AsUTF8(objectsRepresentation);
469 Py_CLEAR(objectsRepresentation);
471 info->
config = json_dumps(dValue);
492 "in plugin_info_fn, plugin '%s'",
493 gPluginName.c_str());
498 auto it = pythonModules->find(gPluginName);
499 if (it == pythonModules->end() ||
501 !it->second->m_module)
504 "pModule is NULL for plugin '%s'",
505 gPluginName.c_str());
509 PyGILState_STATE state = PyGILState_Ensure();
512 pFunc = PyObject_GetAttrString(it->second->m_module,
"plugin_info");
516 "in loaded python module '%s', m_module=%p",
517 gPluginName.c_str(), it->second->m_module);
520 if (!pFunc || !PyCallable_Check(pFunc))
523 if (PyErr_Occurred())
529 "in loaded python module '%s'",
530 gPluginName.c_str());
533 PyGILState_Release(state);
538 PyObject* pReturn = PyObject_CallFunction(pFunc, NULL);
548 ": error while getting result object, plugin '%s'",
549 gPluginName.c_str());
556 info = Py2C_PluginInfo(pReturn);
570 "Updating interface version "
571 "from '%s' to '2.0.0', plugin '%s'",
573 gPluginName.c_str());
575 char *valStr =
new char[6];
576 std::strcpy(valStr,
"2.0.0");
581 "version=%s, options=%d, type=%s, interface=%s, config=%s}",
590 PyGILState_Release(state);
604 string pName = config->getValue(
"plugin");
609 "in plugin_init_fn, plugin '%s'",
615 "config->itemsToJSON()='%s'",
618 bool loadModule =
false;
619 bool reloadModule =
false;
620 bool pythonInitState =
false;
621 string loadPluginType;
624 PyThreadState* newInterp = NULL;
627 for (
auto h = pythonHandles->begin();
628 h != pythonHandles->end();
631 if (h->second->m_name.compare(pName) == 0)
634 "a plugin with name '%s'. Loading a new ",
635 h->second->m_type.c_str(),
639 pythonInitState = h->second->m_init;
642 loadPluginType = h->second->m_type;
654 auto it = pythonModules->find(pName);
655 if (it == pythonModules->end())
658 "pModule not found for plugin '%s': ",
669 loadPluginType = string(pInfo->
type);
678 if (it->second && it->second->m_module)
684 pythonInitState = it->second->m_init;
689 "found pModule is NULL for plugin '%s': ",
697 __FUNCTION__, __LINE__, loadModule?
"TRUE":
"FALSE", reloadModule?
"TRUE":
"FALSE");
700 PyGILState_STATE state = PyGILState_Ensure();
703 if (loadModule || reloadModule)
705 string fledgePythonDir;
707 string fledgeRootDir(getenv(
"FLEDGE_ROOT"));
708 fledgePythonDir = fledgeRootDir +
"/python";
714 PyObject* sysPath = PySys_GetObject((
char *)
"path");
715 PyList_Append(sysPath, PyUnicode_FromString((
char *) fledgePythonDir.c_str()));
719 argv[0] = Py_DecodeLocale(
"", NULL);
720 argv[1] = Py_DecodeLocale(pName.c_str(), NULL);
723 argv[2] = Py_DecodeLocale(loadPluginType.c_str(), NULL);
727 PySys_SetArgv(argc, argv);
730 loadPluginType.c_str(),
731 reloadModule ?
"re-" :
"",
735 PyObject *newObj = PyImport_ImportModule(pName.c_str());
749 PyGILState_Release(state);
752 "failed to create Python module "
753 "object, plugin '%s'",
766 PyGILState_Release(state);
769 "failed to import plugin '%s'",
776 loadPluginType.c_str(),
777 module->m_name.c_str(),
781 set_loglevel_in_python_module(module->m_module, module->m_name +
" plugin_init");
783 PyObject *config_dict = json_loads(config->
itemsToJSON().c_str());
786 PyObject* pReturn = PyObject_CallMethod(module->m_module,
791 Py_CLEAR(config_dict);
797 "error while getting result object, plugin '%s'",
804 "got handle from python plugin='%p', *handle %p, plugin '%s'",
811 std::pair<std::map<PLUGIN_HANDLE, PythonModule*>::iterator,
bool> ret;
815 ret = pythonHandles->insert(pair<PLUGIN_HANDLE, PythonModule*>
816 ((PLUGIN_HANDLE)pReturn, module));
821 "handle %p of python plugin '%s' "
822 "added to pythonHandles map",
829 "failed to insert handle %p of "
830 "python plugin '%s' to pythonHandles map",
834 Py_CLEAR(module->m_module);
835 module->m_module = NULL;
847 PyEval_ReleaseThread(newInterp);
851 PyGILState_Release(state);
854 return pReturn ? (PLUGIN_HANDLE) pReturn : NULL;
863 static void plugin_reconfigure_fn(PLUGIN_HANDLE* handle,
864 const std::string& config)
878 "in plugin_reconfigure, plugin handle '%p'",
884 auto it = pythonHandles->find(*handle);
885 if (it == pythonHandles->end() ||
887 !it->second->m_module)
890 "pModule is NULL, plugin handle '%p'",
897 lock_guard<mutex> guard(mtx);
898 PyGILState_STATE state = PyGILState_Ensure();
901 "pModule=%p, *handle=%p, plugin '%s'",
902 it->second->m_module,
904 it->second->m_name.c_str());
906 if(config.compare(
"logLevel") == 0)
909 set_loglevel_in_python_module(it->second->m_module, it->second->m_name+
" plugin_reconf");
910 PyGILState_Release(state);
915 pFunc = PyObject_GetAttrString(it->second->m_module,
"plugin_reconfigure");
919 "in loaded python module '%s'",
920 it->second->m_name.c_str());
923 if (!pFunc || !PyCallable_Check(pFunc))
926 if (PyErr_Occurred())
932 "in loaded python module '%s'",
933 it->second->m_name.c_str());
936 PyGILState_Release(state);
942 PyObject *new_config_dict = json_loads(config.c_str());
945 PyObject* pReturn = PyObject_CallFunction(pFunc,
951 Py_CLEAR(new_config_dict);
957 ": error while getting result object, plugin '%s'",
958 it->second->m_name.c_str());
973 pythonHandles->erase(it);
976 std::pair<std::map<PLUGIN_HANDLE, PythonModule*>::iterator,
bool> ret;
977 ret = pythonHandles->insert(pair<PLUGIN_HANDLE, PythonModule*>
978 ((PLUGIN_HANDLE)*handle, currentModule));
981 "updated handle %p of python plugin '%s'"
982 " in pythonHandles map",
984 currentModule->m_name.c_str());
989 "failed to update handle %p of python plugin '%s'"
990 " in pythonHandles map",
992 currentModule->m_name.c_str());
996 PyGILState_Release(state);
1003 static void logErrorMessage()
1007 PyObject* traceback;
1009 numpyImportError =
false;
1011 PyErr_Fetch(&type, &value, &traceback);
1012 PyErr_NormalizeException(&type, &value, &traceback);
1014 PyObject* str_exc_value = PyObject_Repr(value);
1015 PyObject* pyExcValueStr = PyUnicode_AsEncodedString(str_exc_value,
"utf-8",
"Error ~");
1016 const char* pErrorMessage = value ?
1017 PyBytes_AsString(pyExcValueStr) :
1018 "no error description.";
1021 gPluginName.c_str());
1024 const char *err1 =
"implement_array_function method already has a docstring";
1025 const char *err2 =
"cannot import name 'check_array_indexer' from 'pandas.core.indexers'";
1027 numpyImportError = strstr(pErrorMessage, err1) || strstr(pErrorMessage, err2);
1029 std::string fcn =
"";
1030 fcn +=
"def get_pretty_traceback(exc_type, exc_value, exc_tb):\n";
1031 fcn +=
" import sys, traceback\n";
1032 fcn +=
" lines = []\n";
1033 fcn +=
" lines = traceback.format_exception(exc_type, exc_value, exc_tb)\n";
1034 fcn +=
" output = '\\n'.join(lines)\n";
1035 fcn +=
" return output\n";
1037 PyRun_SimpleString(fcn.c_str());
1038 PyObject* mod = PyImport_ImportModule(
"__main__");
1040 PyObject* method = PyObject_GetAttrString(mod,
"get_pretty_traceback");
1041 if (method != NULL) {
1042 PyObject* outStr = PyObject_CallObject(method, Py_BuildValue(
"OOO", type, value, traceback));
1043 if (outStr != NULL) {
1044 PyObject* tmp = PyUnicode_AsASCIIString(outStr);
1046 std::string pretty = PyBytes_AsString(tmp);
1063 Py_CLEAR(traceback);
1064 Py_CLEAR(str_exc_value);
1065 Py_CLEAR(pyExcValueStr);
1074 static void plugin_shutdown_fn(PLUGIN_HANDLE handle)
1086 "in plugin_shutdown_fn, plugin handle '%p'",
1092 auto it = pythonHandles->find(handle);
1093 if (it == pythonHandles->end() ||
1095 !it->second->m_module)
1098 "pModule is NULL, plugin handle '%p'",
1103 if (! Py_IsInitialized()) {
1105 Logger::getLogger()->
debug(
"%s - Python environment not initialized, exiting from the function ", __FUNCTION__);
1110 PyGILState_STATE state = PyGILState_Ensure();
1113 pFunc = PyObject_GetAttrString(it->second->m_module,
"plugin_shutdown");
1117 "in loaded python module '%s'",
1118 it->second->m_name.c_str());
1121 if (!pFunc || !PyCallable_Check(pFunc))
1124 if (PyErr_Occurred())
1130 "in loaded python module '%s'",
1131 it->second->m_name.c_str());
1134 PyGILState_Release(state);
1139 PyObject* pReturn = PyObject_CallFunction(pFunc,
1149 PyThreadState* swapState = PyThreadState_Swap(it->second->m_tState);
1152 Py_CLEAR(it->second->m_module);
1153 it->second->m_module = NULL;
1156 Py_EndInterpreter(it->second->m_tState);
1160 it->second->m_tState,
1161 it->second->m_name.c_str());
1163 PyThreadState_Swap(swapState);
1166 it->second->m_tState = NULL;
1171 Py_CLEAR(it->second->m_module);
1172 it->second->m_module = NULL;
1176 string pName = it->second->m_name;
1179 pythonHandles->erase(it);
1182 auto m = pythonModules->find(pName);
1183 if (m != pythonModules->end())
1186 pythonModules->erase(m);
1194 PyGILState_Release(state);
1197 "called for plugin '%s'",