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;
104 Logger::getLogger()->error(
"pythonModules map is NULL " 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;
174 Logger::getLogger()->debug(
"Removing Python interpreter " 175 "started by plugin '%s'",
183 PyGILState_Release(state);
186 Logger::getLogger()->debug(
"PluginInterfaceCleanup succesfully " 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)
208 string& _loglevel = Logger::getLogger()->getMinLevel();
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);
235 Logger::getLogger()->warn(
"%s: _LOGGER.setLevel(%s) failed", __FUNCTION__, loglevel);
236 if (PyErr_Occurred())
245 Logger::getLogger()->warn(
"%s: Method 'setLevel' not found", __FUNCTION__);
251 Logger::getLogger()->warn(
"%s: Object '_LOGGER' not found in python module", __FUNCTION__);
255 Logger::getLogger()->warn(
"%s: module is NULL", __FUNCTION__);
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())
291 Logger::getLogger()->info(
"%s:%d, rval type=%s", __FUNCTION__, __LINE__, (Py_TYPE(rval))->tp_name);
296 Logger::getLogger()->fatal(
"Method 'dumps' not found");
304 Logger::getLogger()->fatal(
"Failed to import module");
310 PyGILState_Release(state);
312 const char *retVal = PyUnicode_AsUTF8(rval);
313 Logger::getLogger()->debug(
"%s: retVal=%s", __FUNCTION__, retVal);
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())
351 Logger::getLogger()->debug(
"%s:%d, rval type=%s", __FUNCTION__, __LINE__, (Py_TYPE(rval))->tp_name);
356 Logger::getLogger()->fatal(
"Method 'loads' not found");
362 Logger::getLogger()->fatal(
"Failed to import module");
368 PyGILState_Release(state);
388 PyObject *dKey, *dValue;
391 PyObject* objectsRepresentation = PyObject_Repr(pyRetVal);
392 const char* s = PyUnicode_AsUTF8(objectsRepresentation);
393 Logger::getLogger()->debug(
"Py2C_PluginInfo(): plugin_info returned: %s", s);
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,
'|'))
430 Logger::getLogger()->debug(
"%s: mode: Found token %s", __FUNCTION__, s.c_str());
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)
448 Logger::getLogger()->warn(
"%s: mode: Unknown token/value %s", __FUNCTION__, s.c_str());
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);
468 Logger::getLogger()->debug(
"Py2C_PluginInfo(): INPUT: config value=%s", s);
469 Py_CLEAR(objectsRepresentation);
471 info->
config = json_dumps(dValue);
472 Logger::getLogger()->info(
"Py2C_PluginInfo(): OUTPUT: config value=%s", info->
config);
478 Logger::getLogger()->info(
"%s:%d: Unexpected key %s", __FUNCTION__, __LINE__, ckey);
491 Logger::getLogger()->error(
"pythonModules map is NULL " 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)
503 Logger::getLogger()->fatal(
"plugin_handle: plugin_info(): " 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");
515 Logger::getLogger()->fatal(
"Cannot find method '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())
528 Logger::getLogger()->fatal(
"Cannot call method 'plugin_info' " 529 "in loaded python module '%s'",
530 gPluginName.c_str());
533 PyGILState_Release(state);
538 PyObject* pReturn = PyObject_CallFunction(pFunc, NULL);
547 Logger::getLogger()->error(
"Called python script method 'plugin_info' " 548 ": error while getting result object, plugin '%s'",
549 gPluginName.c_str());
556 info = Py2C_PluginInfo(pReturn);
569 Logger::getLogger()->info(
"plugin_handle: plugin_info(): " 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");
580 Logger::getLogger()->info(
"plugin_handle: plugin_info(): info={name=%s, " 581 "version=%s, options=%d, type=%s, interface=%s, config=%s}",
590 PyGILState_Release(state);
604 string pName = config->
getValue(
"plugin");
608 Logger::getLogger()->error(
"pythonModules map is NULL " 609 "in plugin_init_fn, plugin '%s'",
614 Logger::getLogger()->debug(
"plugin_handle: plugin_init(): " 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)
633 Logger::getLogger()->debug(
"%s_plugin_init_fn: already loaded " 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())
657 Logger::getLogger()->debug(
"plugin_handle: plugin_init(): " 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;
688 Logger::getLogger()->fatal(
"plugin_handle: plugin_init(): " 689 "found pModule is NULL for plugin '%s': ",
696 Logger::getLogger()->info(
"%s:%d: loadModule=%s, reloadModule=%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);
729 Logger::getLogger()->debug(
"%s_plugin_init_fn, %sloading plugin '%s', ",
730 loadPluginType.c_str(),
731 reloadModule ?
"re-" :
"",
735 PyObject *newObj = PyImport_ImportModule(pName.c_str());
749 PyGILState_Release(state);
751 Logger::getLogger()->fatal(
"plugin_handle: plugin_init(): " 752 "failed to create Python module " 753 "object, plugin '%s'",
766 PyGILState_Release(state);
768 Logger::getLogger()->fatal(
"plugin_handle: plugin_init(): " 769 "failed to import plugin '%s'",
775 Logger::getLogger()->debug(
"%s_plugin_init_fn for '%s', pModule '%p', ",
776 loadPluginType.c_str(),
777 module->m_name.c_str(),
780 Logger::getLogger()->debug(
"%s:%d: calling set_loglevel_in_python_module(), loglevel=%s", __FUNCTION__, __LINE__, Logger::getLogger()->getMinLevel().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);
796 Logger::getLogger()->error(
"Called python script method plugin_init : " 797 "error while getting result object, plugin '%s'",
803 Logger::getLogger()->debug(
"plugin_handle: plugin_init(): " 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));
820 Logger::getLogger()->debug(
"plugin_handle: plugin_init(): " 821 "handle %p of python plugin '%s' " 822 "added to pythonHandles map",
828 Logger::getLogger()->error(
"plugin_handle: plugin_init(): " 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)
866 Logger::getLogger()->debug(
"%s:%d: config=%s", __FUNCTION__, __LINE__, config.c_str());
870 Logger::getLogger()->fatal(
"plugin_handle: plugin_reconfigure(): " 877 Logger::getLogger()->error(
"pythonHandles map is NULL " 878 "in plugin_reconfigure, plugin handle '%p'",
884 auto it = pythonHandles->find(*handle);
885 if (it == pythonHandles->end() ||
887 !it->second->m_module)
889 Logger::getLogger()->fatal(
"plugin_handle: plugin_reconfigure(): " 890 "pModule is NULL, plugin handle '%p'",
897 lock_guard<mutex> guard(mtx);
898 PyGILState_STATE state = PyGILState_Ensure();
900 Logger::getLogger()->debug(
"plugin_handle: plugin_reconfigure(): " 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)
908 Logger::getLogger()->debug(
"calling set_loglevel_in_python_module() for updating loglevel");
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");
918 Logger::getLogger()->fatal(
"Cannot find method '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())
931 Logger::getLogger()->fatal(
"Cannot call method plugin_reconfigure " 932 "in loaded python module '%s'",
933 it->second->m_name.c_str());
936 PyGILState_Release(state);
940 Logger::getLogger()->debug(
"plugin_reconfigure with %s", config.c_str());
942 PyObject *new_config_dict = json_loads(config.c_str());
945 PyObject* pReturn = PyObject_CallFunction(pFunc,
951 Py_CLEAR(new_config_dict);
956 Logger::getLogger()->error(
"Called python script method plugin_reconfigure " 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));
980 Logger::getLogger()->debug(
"plugin_handle: plugin_reconfigure(): " 981 "updated handle %p of python plugin '%s'" 982 " in pythonHandles map",
984 currentModule->m_name.c_str());
988 Logger::getLogger()->error(
"plugin_handle: plugin_reconfigure(): " 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.";
1019 Logger::getLogger()->warn(
"logErrorMessage: Error '%s', plugin '%s'",
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);
1047 Logger::getLogger()->warn(
"%s", pretty.c_str());
1048 Logger::getLogger()->printLongString(pretty.c_str());
1063 Py_CLEAR(traceback);
1064 Py_CLEAR(str_exc_value);
1065 Py_CLEAR(pyExcValueStr);
1074 static void plugin_shutdown_fn(PLUGIN_HANDLE handle)
1078 Logger::getLogger()->fatal(
"plugin_handle: plugin_shutdown_fn: " 1085 Logger::getLogger()->error(
"pythonHandles map is NULL " 1086 "in plugin_shutdown_fn, plugin handle '%p'",
1092 auto it = pythonHandles->find(handle);
1093 if (it == pythonHandles->end() ||
1095 !it->second->m_module)
1097 Logger::getLogger()->fatal(
"plugin_handle: plugin_shutdown_fn: " 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");
1116 Logger::getLogger()->fatal(
"Cannot find method '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())
1129 Logger::getLogger()->fatal(
"Cannot call method 'plugin_shutdown' " 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);
1158 Logger::getLogger()->debug(
"plugin_shutdown_fn: Py_EndInterpreter of '%p' " 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);
1196 Logger::getLogger()->debug(
"plugin_shutdown_fn succesfully " 1197 "called for plugin '%s'",
static PluginManager * getInstance()
PluginManager Singleton implementation.
Definition: plugin_manager.cpp:43
Definition: config_category.h:56
The manager for plugins.
Definition: plugin_manager.h:40
Definition: asset_tracking.h:108
std::string getValue(const std::string &name) const
Return the value of the configuration category item.
Definition: config_category.cpp:449
std::string itemsToJSON(const bool full=false) const
Return JSON string of category items only.
Definition: config_category.cpp:1159
PLUGIN_INFORMATION * getInfo(const PLUGIN_HANDLE)
Return the information for a named plugin.
Definition: plugin_manager.cpp:531
This class represents the loaded Python module with interpreter initialisation flag.
Definition: python_plugin_common_interface.h:27
PLUGIN_HANDLE findPluginByName(const std::string &name)
Find a loaded plugin by name.
Definition: plugin_manager.cpp:507