@@ -103,7 +103,7 @@ static std::recursive_mutex python_mutex;
103103
104104namespace
105105{
106- const char * const rule_engine_name = " python" ;
106+ static inline constexpr const char * const rule_engine_name = " python" ;
107107 using log_re = irods::experimental::log::rule_engine;
108108
109109 // Python thread states and other data from the interpreter
@@ -118,6 +118,13 @@ namespace
118118 static thread_local PyThreadState* ts_thread_old;
119119 // Reference counter for nested python operations
120120 static thread_local uint64_t ts_thread_refct = 0 ;
121+
122+ #if PY_VERSION_HEX >= 0x03080000
123+ // List of default module search paths
124+ static std::vector<std::wstring> default_module_search_paths;
125+ // Whether or not default_module_search_paths is populated
126+ static bool default_module_search_paths_set = false ;
127+ #endif
121128 } // namespace python_state
122129}
123130
@@ -378,6 +385,52 @@ namespace
378385 bp::class_<CallbackWrapper>(" CallbackWrapper" , bp::no_init)
379386 .def (" __getattribute__" , &CallbackWrapper::getAttribute);
380387 }
388+
389+ static inline void initialize_python (const std::string& _instance_name)
390+ {
391+ auto etc_irods_path = irods::get_irods_config_directory ();
392+
393+ #if PY_VERSION_HEX >= 0x03080000
394+ if (python_state::default_module_search_paths_set) {
395+ PyConfig py_config;
396+ PyConfig_InitPythonConfig (&py_config);
397+
398+ py_config.install_signal_handlers = 0 ;
399+
400+ for (auto && module_search_path : python_state::default_module_search_paths) {
401+ PyWideStringList_Append (&py_config.module_search_paths , module_search_path.c_str ());
402+ }
403+ PyWideStringList_Append (&py_config.module_search_paths , etc_irods_path.generic_wstring ().c_str ());
404+ py_config.module_search_paths_set = 1 ;
405+
406+ PyStatus py_status = Py_InitializeFromConfig (&py_config);
407+
408+ if (!PyStatus_Exception (py_status)) {
409+ return ;
410+ }
411+
412+ // clang-format off
413+ log_re::error ({
414+ {" rule_engine_plugin" , rule_engine_name},
415+ {" instance_name" , _instance_name},
416+ {" log_message" , " failed to initialize interpreter with PyConfig; falling back to legacy initialization" },
417+ {" PyStatus.exitcode" , fmt::to_string (py_status.exitcode )},
418+ {" PyStatus.err_msg" , py_status.err_msg },
419+ {" PyStatus.func" , py_status.func },
420+ });
421+ // clang-format on
422+ }
423+ #endif
424+
425+ Py_InitializeEx (0 );
426+ #if PY_VERSION_HEX < 0x03070000
427+ PyEval_InitThreads ();
428+ #endif
429+ bp::object mod_sys = bp::import (" sys" );
430+ bp::list sys_path = bp::extract<bp::list>(mod_sys.attr (" path" ));
431+ sys_path.append (bp::str (etc_irods_path.generic_string ().c_str ()));
432+ }
433+
381434} // anonymous namespace
382435
383436static irods::error start (irods::default_re_ctx&, const std::string& _instance_name)
@@ -391,16 +444,8 @@ static irods::error start(irods::default_re_ctx&, const std::string& _instance_n
391444 PyImport_AppendInittab (" plugin_wrappers" , &PyInit_plugin_wrappers);
392445 PyImport_AppendInittab (" irods_types" , &PyInit_irods_types);
393446 PyImport_AppendInittab (" irods_errors" , &PyInit_irods_errors);
394- Py_InitializeEx (0 );
395- #if PY_VERSION_HEX < 0x03070000
396- PyEval_InitThreads ();
397- #endif
398- boost::filesystem::path etc_irods_path = irods::get_irods_config_directory ();
399- std::string exec_str = " import sys\n sys.path.append('" + etc_irods_path.generic_string () + " ')" ;
400447
401- bp::object main_module = bp::import (" __main__" );
402- bp::object main_namespace = main_module.attr (" __dict__" );
403- bp::exec (exec_str.c_str (), main_namespace);
448+ initialize_python (_instance_name);
404449
405450 bp::object plugin_wrappers = bp::import (" plugin_wrappers" );
406451 bp::object irods_types = bp::import (" irods_types" );
@@ -1022,5 +1067,34 @@ extern "C" irods::pluggable_rule_engine<irods::default_re_ctx>* plugin_factory(c
10221067 std::function<irods::error (irods::default_re_ctx&, const std::string&, msParamArray_t*, irods::callback)>(
10231068 exec_rule_expression));
10241069
1070+ #if PY_VERSION_HEX >= 0x03080000
1071+ Py_InitializeEx (0 );
1072+ try {
1073+ bp::object mod_sys = bp::import (" sys" );
1074+ bp::list sys_path = bp::extract<bp::list>(mod_sys.attr (" path" ));
1075+ std::size_t sys_path_len = bp::extract<std::size_t >(sys_path.attr (" __len__" )());
1076+ python_state::default_module_search_paths.reserve (sys_path_len);
1077+ for (std::size_t i = 0 ; i < sys_path_len; ++i) {
1078+ python_state::default_module_search_paths.push_back (bp::extract<std::wstring>(sys_path[i]));
1079+ }
1080+ python_state::default_module_search_paths_set = true ;
1081+ }
1082+ catch (const bp::error_already_set&) {
1083+ const std::string formatted_python_exception = extract_python_exception ();
1084+ // clang-format off
1085+ log_re::error ({
1086+ {" rule_engine_plugin" , rule_engine_name},
1087+ {" instance_name" , _inst_name},
1088+ {" log_message" , " caught python exception in plugin factory; will use legacy module search path initialization" },
1089+ {" python_exception" , formatted_python_exception},
1090+ });
1091+ // clang-format on
1092+ python_state::default_module_search_paths.clear ();
1093+ python_state::default_module_search_paths.shrink_to_fit ();
1094+ python_state::default_module_search_paths_set = false ;
1095+ }
1096+ Py_Finalize (); // We're not supposed to do this, but let's try it anyway.
1097+ #endif
1098+
10251099 return re;
10261100}
0 commit comments