From e5f670e28954e33b305861fabfd7ca6f445b5885 Mon Sep 17 00:00:00 2001 From: Saeed Amrollahi Boyouki Date: Thu, 27 Apr 2023 15:15:26 +0330 Subject: [PATCH] feat: write wrapper for boost python issue #461 --- wolf/CMakeLists.txt | 2 + wolf/cmake/system.cmake | 10 + wolf/system/script/w_python.cpp | 1 + wolf/system/script/w_python.hpp | 485 ++++++++++++++++++++++++++++++++ wolf/system/test/python.hpp | 57 ++++ 5 files changed, 555 insertions(+) create mode 100644 wolf/system/script/w_python.cpp create mode 100644 wolf/system/script/w_python.hpp create mode 100644 wolf/system/test/python.hpp diff --git a/wolf/CMakeLists.txt b/wolf/CMakeLists.txt index 24c7dc7b1..a841b3743 100644 --- a/wolf/CMakeLists.txt +++ b/wolf/CMakeLists.txt @@ -106,6 +106,7 @@ option(WOLF_SYSTEM_LOG "Enable log" ON) option(WOLF_SYSTEM_LZ4 "Enable lz4 for compression" ON) option(WOLF_SYSTEM_LZMA "Enable lzma for compression" ON) #option(WOLF_SYSTEM_LUA "Enable lua scripting language" OFF) +option(WOLF_SYSTEM_PYTHON "Enable python scripting language" OFF) option(WOLF_SYSTEM_MIMALLOC "Enable Microsoft's mimalloc memory allocator" ON) option(WOLF_SYSTEM_POSTGRESQL "Enable postgresql database client" OFF) option(WOLF_SYSTEM_PYTHON "Enable embedded Python3 scripting" OFF) @@ -305,6 +306,7 @@ source_group("system/gamepad" FILES ${WOLF_SYSTEM_GAMEPAD_CLIENT_SRC} ${WOLF_SYS source_group("system/log" FILES ${WOLF_SYSTEM_LOG_SRC}) source_group("system/compression" FILES ${WOLF_SYSTEM_LZ4_SRCS} ${WOLF_SYSTEM_LZMA_SRCS}) source_group("system/script" FILES ${WOLF_SYSTEM_LUA_SRC}) +source_group("system/script" FILES ${WOLF_SYSTEM_PYTHON_SRC}) source_group("system/socket" FILES ${WOLF_SYSTEM_SOCKET_SRC} ${WOLF_SYSTEM_HTTP_WS_SRC}) source_group("system/test" FILES ${WOLF_SYSTEM_TEST_SRC}) source_group("system" FILES ${WOLF_SYSTEM_SRC}) diff --git a/wolf/cmake/system.cmake b/wolf/cmake/system.cmake index 42cfc3b5a..530a99d37 100644 --- a/wolf/cmake/system.cmake +++ b/wolf/cmake/system.cmake @@ -236,6 +236,16 @@ endif() # ${WOLF_SYSTEM_LUA_SRC} #) #endif() +if (WOLF_SYSTEM_PYTHON) + file(GLOB_RECURSE WOLF_SYSTEM_PYTHON_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/system/script/w_python.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/script/w_python.cpp" + ) + + list(APPEND SRCS + ${WOLF_SYSTEM_PYTHON_SRC} + ) +endif() if (EMSCRIPTEN) file (GLOB_RECURSE WOLF_SYSTEM_SRC diff --git a/wolf/system/script/w_python.cpp b/wolf/system/script/w_python.cpp new file mode 100644 index 000000000..80b2c7dfb --- /dev/null +++ b/wolf/system/script/w_python.cpp @@ -0,0 +1 @@ +#include "w_python.hpp" diff --git a/wolf/system/script/w_python.hpp b/wolf/system/script/w_python.hpp new file mode 100644 index 000000000..78b651b36 --- /dev/null +++ b/wolf/system/script/w_python.hpp @@ -0,0 +1,485 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace python_wrapper { +#ifdef USE_PY_FINALIZE +#undef USE_PY_FINALIZE +#endif +std::string load_py_file(const std::filesystem::path& /* python file */); +void run_simple_script(const std::string& /* script */); +void run_simple_script_from_file(const std::filesystem::path& /* python file */); +template [[nodiscard("No ignore return value!")]] +T run_function_and_get_result(const std::string& /* script */, const std::string& /* function name */); +template [[nodiscard("No ignore return value!")]] +T run_function_from_file_script_and_get_result(const std::filesystem::path& /* python file */, + const std::string& /* function name */); +template [[nodiscard("No ignore return value!")]] +boost::python::tuple call_function(const std::string& /* function name */, + const std::string& /* script */, + ARGS&&... /* arguments */); +/* + * helper functions + */ +/* + * convert boost python tuple to standard tuple + */ +template +std::tuple convertBPTuple2StdTuple(const boost::python::tuple&); + +const std::string default_module_name{"__main__"}; +const std::string default_dictionary_name{"__dict__"}; +const std::string bad_python_script{"Bad python script! Maybe syntax error"}; +const std::string bad_filename{"Bad filename! File not found"}; +#ifdef USE_PY_FINALIZE +const std::string bad_python_finalization{"Python finalization failed"}; +#endif // USE_PY_FINALIZE +class PythonRunner { // a class wrapper used by Boost.Python + std::string script_; // python script + std::string module_name_; + std::string dictionary_name_; + /* + * prohibit copy operations + */ + PythonRunner(const PythonRunner&) =delete; + PythonRunner& operator=(const PythonRunner&) =delete; +public: + ~PythonRunner() =default; + PythonRunner() : script_{}, + module_name_{default_module_name}, + dictionary_name_{default_dictionary_name} + { + } + explicit PythonRunner(const std::string& s, + const std::string& m = default_module_name, + const std::string& d = default_dictionary_name) : + script_(s), module_name_{m}, dictionary_name_{d} + { + } + std::string script() const + { + return script_; + } + PythonRunner& set_script(const std::string& s) + { + script_ = s; + return *this; + } + + PythonRunner& set_module(const std::string& m) + { + module_name_ = m; + return *this; + } + + PythonRunner& set_dictionary(const std::string& d) + { + dictionary_name_ = d; + return *this; + } + /** + * run simple python script: no input, no output + */ + /* + * run an inline python script: no input parameter, no output parameter + */ + void run_simple_script(const std::string& /* script */ = {}); + /* + * run a python script from a file: no input parameter, no output parameter + */ + void run_simple_script_file(const std::filesystem::path& /* path file */); + /* + * run a function from inline python script: no input parameter, one output parameter + */ + template [[nodiscard("No ignore return value!")]] + RETTYPE run_function_and_get_result(const std::string& /* function name */, + const std::string& /* script */ = {}); + /* + * run a function from a script file : no input parameter, one output parameter + */ + template [[nodiscard("No ignore return value!")]] + RETTYPE run_function_from_script_file_and_get_result(const std::filesystem::path& path_file, + const std::string& func_name); + /* + * run a function from inline python script: variadic number of input parameter, variadic number of output parameter + */ + template + [[nodiscard("No ignore return value!")]] + boost::python::tuple call_function(const std::string& func_name, + const std::string& /* script */ = {}, + ARGS&&... args); + /* + * run a function from a script file: variadic number of input parameter, variadic number of output parameter + */ + template + [[nodiscard("No ignore return value!")]] + boost::python::tuple call_function_from_script_file(const std::filesystem::path& path_file, + const std::string& func_name, + ARGS&&... args); + /* + * run a function from inline python script and return the value of single variable + */ + template + [[nodiscard("No ignore return value!")]] + T run_and_get_value(const std::string& /* variable name */, + const std::string& /* script */ = {}); + + /* + * run a function from a script file and return the value of single variable + */ + template + [[nodiscard("No ignore return value!")]] + T run_file_script_and_get_value(const std::filesystem::path& /* python file */, + const std::string& /* variable name */); +}; // of PythonRunner + +void PythonRunner::run_simple_script(const std::string& script) +{ + if (!script.empty()) + script_ = script; + Py_Initialize(); + auto run_return_val = PyRun_SimpleString(script_.c_str()); + if (run_return_val < 0) + throw std::runtime_error{bad_python_script}; +#ifdef USE_PY_FINALIZE + if (Py_FinalizeEx() < 0) { + throw std::runtime_error{bad_python_finalization}; + } +#endif // USE_PY_FINALIZE +} + +void PythonRunner::run_simple_script_file(const std::filesystem::path &p_file) +{ + script_ = load_py_file(p_file); + run_simple_script(); +} + +template [[nodiscard("No ignore return value!")]] +RETTYPE PythonRunner::run_function_and_get_result(const std::string& func_name, + const std::string& script) +{ + if (!script.empty()) + script_ = script; + Py_Initialize(); + namespace bp = boost::python; + RETTYPE val{}; + try { + bp::object main = bp::import(module_name_.c_str()); + bp::object global(main.attr(dictionary_name_.c_str())); + bp::object result = bp::exec(script_.c_str(), global, global); + bp::object func = global[func_name.c_str()]; // Create a reference to it. + val = bp::extract(func()); + return val; + } + catch(bp::error_already_set&) + { + PyErr_Print(); + throw; +#ifdef USE_PY_FINALIZE + if (Py_FinalizeEx() < 0) { + PyErr_Print(); + throw std::runtime_error{bad_python_finalization}; + } +#endif // USE_PY_FINALIZE + } +#ifdef USE_PY_FINALIZE + if (Py_FinalizeEx() < 0) { + PyErr_Print(); + throw std::runtime_error{bad_python_finalization}; + } +#endif // USE_PY_FINALIZE + return val; +} + +template [[nodiscard("No ignore return value!")]] +RETTYPE PythonRunner::run_function_from_script_file_and_get_result(const std::filesystem::path& path_file, + const std::string& func_name) +{ + script_ = load_py_file(path_file); + Py_Initialize(); + namespace bp = boost::python; + RETTYPE ret{}; + try { + bp::object main = bp::import(module_name_.c_str()); + bp::object global(main.attr(dictionary_name_.c_str())); + std::wstring path_as_wide_str = path_file.c_str(); + std::string path_as_str; + std::copy(path_as_wide_str.begin(), path_as_wide_str.end(), std::back_inserter(path_as_str)); + bp::object result = bp::exec_file(path_as_str.c_str(), global, global); + bp::object func = global[func_name.c_str()]; // Create a reference to it. + ret = bp::extract(func()); + return ret; + } + catch(bp::error_already_set&) + { + PyErr_Print(); + throw; +#ifdef USE_PY_FINALIZE + if (Py_FinalizeEx() < 0) { + PyErr_Print(); + throw std::runtime_error{bad_python_finalization}; + } +#endif // USE_PY_FINALIZE + } +#ifdef USE_PY_FINALIZE + if (Py_FinalizeEx() < 0) { + PyErr_Print(); + throw std::runtime_error{bad_python_finalization}; + } +#endif // USE_PY_FINALIZE + return ret; +} + +template +[[nodiscard]] boost::python::tuple PythonRunner::call_function( + const std::string& func_name, const std::string& script, ARGS&&... args) +{ + if (!script.empty()) + script_ = script; + + Py_Initialize(); + namespace bp = boost::python; + bp::tuple ret{}; + try { + bp::object main = bp::import(module_name_.c_str()); + bp::object global(main.attr(dictionary_name_.c_str())); + + bp::object result = bp::exec(script_.c_str(), global, global); + bp::object func = global[func_name.c_str()]; // Create a reference to it. + ret = bp::call(func.ptr(), args...); + } + catch(bp::error_already_set&) + { + PyErr_Print(); + throw; +#ifdef USE_PY_FINALIZE + if (Py_FinalizeEx() < 0) { + PyErr_Print(); + throw std::runtime_error{bad_python_finalization}; + } +#endif // USE_PY_FINALIZE + } +#ifdef USE_PY_FINALIZE + if (Py_FinalizeEx() < 0) { + PyErr_Print(); + throw std::runtime_error{bad_python_finalization}; + } +#endif // USE_PY_FINALIZE + return ret; +} + +template +[[nodiscard("No ignore return value!")]] +boost::python::tuple PythonRunner::call_function_from_script_file(const std::filesystem::path& path_file, + const std::string& func_name, + ARGS&&... args) +{ + script_ = load_py_file(path_file); + Py_Initialize(); + namespace bp = boost::python; + bp::tuple ret{}; + try { + bp::object main = bp::import(module_name_.c_str()); + bp::object global(main.attr(dictionary_name_.c_str())); + bp::object result = bp::exec_file(path_file.filename().c_str(), global, global); + bp::object func = global[func_name.c_str()]; // Create a reference to it. + ret = bp::extract(func()); + return ret; + } + catch(bp::error_already_set&) + { + PyErr_Print(); + throw; +#ifdef USE_PY_FINALIZE + if (Py_FinalizeEx() < 0) { + PyErr_Print(); + throw std::runtime_error{bad_python_finalization}; + } +#endif // USE_PY_FINALIZE + } +#ifdef USE_PY_FINALIZE + if (Py_FinalizeEx() < 0) { + PyErr_Print(); + throw std::runtime_error{bad_python_finalization}; + } +#endif // USE_PY_FINALIZE + return ret; +} + +template +[[nodiscard("No ignore return value!")]] +T PythonRunner::run_and_get_value(const std::string& var_name, const std::string& script) +{ + if (!script.empty()) + script_ = script; + + Py_Initialize(); + namespace bp = boost::python; + try { + boost::python::object main = boost::python::import("__main__"); + boost::python::object global(main.attr("__dict__")); + boost::python::exec(script_.c_str(), global); + T val = boost::python::extract(global[var_name]); + + return val; + } + catch(bp::error_already_set&) + { + PyErr_Print(); + throw; +#ifdef USE_PY_FINALIZE + if (Py_FinalizeEx() < 0) { + PyErr_Print(); + throw std::runtime_error{bad_python_finalization}; + } +#endif // USE_PY_FINALIZE + } +#ifdef USE_PY_FINALIZE + if (Py_FinalizeEx() < 0) { + PyErr_Print(); + throw std::runtime_error{bad_python_finalization}; + } +#endif // USE_PY_FINALIZE +} + +template +[[nodiscard("No ignore return value!")]] +T PythonRunner::run_file_script_and_get_value(const std::filesystem::path& path_file, + const std::string& var_name) +{ + script_ = load_py_file(path_file); + Py_Initialize(); + namespace bp = boost::python; + try { + boost::python::object main = boost::python::import("__main__"); + boost::python::object global(main.attr("__dict__")); + bp::object result = bp::exec_file(path_file.filename().c_str(), global, global); + T val = boost::python::extract(global[var_name]); + + return val; + } + catch(bp::error_already_set&) + { + PyErr_Print(); + throw; +#ifdef USE_PY_FINALIZE + if (Py_FinalizeEx() < 0) { + PyErr_Print(); + throw std::runtime_error{bad_python_finalization}; + } +#endif // USE_PY_FINALIZE + } +#ifdef USE_PY_FINALIZE + if (Py_FinalizeEx() < 0) { + PyErr_Print(); + throw std::runtime_error{bad_python_finalization}; + } +#endif // USE_PY_FINALIZE +} +[[nodiscard("No ignore return value!")]] +std::string load_py_file(const std::filesystem::path& path_file) +{ + if (!std::filesystem::exists(path_file)) + throw std::runtime_error{"bad filename!"}; + auto path_file_as_str = path_file.c_str(); + std::ifstream f{path_file_as_str}; + std::string content; + char ch; + while (f.get(ch)) + content.push_back(ch); + + return content; +} + +void run_simple_script(const std::string& script) +{ + PythonRunner pr; + pr.set_script(script); + pr.run_simple_script(); +} + +void run_simple_script_from_file(const std::filesystem::path& python_file) +{ + PythonRunner pr; + pr.run_simple_script_file(python_file); +} + +template +[[nodiscard("No ignore return value!")]] RETTYPE run_function_and_get_result(const std::string& script, const std::string& func_name) +{ + PythonRunner pr{script}; + auto val = pr.run_function_and_get_result(func_name); + return val; +} + +template +[[nodiscard("No ignore return value!")]] RETTYPE run_function_from_file_script_and_get_result(const std::filesystem::path& path_file, + const std::string& func_name) +{ + PythonRunner pr; + auto val = pr.run_function_from_script_file_and_get_result(path_file, func_name); + return val; +} + +template +[[nodiscard("No ignore return value!")]] +boost::python::tuple call_function(const std::string& func_name, + const std::string& script, + ARGS&&... args) +{ + PythonRunner pr; + auto val = pr.call_function(func_name, script, args...); + return val; +} + +template +[[nodiscard("No ignore return value!")]] +boost::python::tuple call_function_from_script_file(const std::filesystem::path& path_file, + const std::string& func_name, + ARGS&&... args) +{ + PythonRunner pr; + auto val = pr.call_function(path_file, func_name, args...); + return val; +} + +template +[[nodiscard("No ignore return value!")]] +T run_and_get_value(const std::string& var_name, const std::string& script) +{ + PythonRunner pr; + auto val = pr.run_and_get_value(var_name, script); + return val; +} + +template +[[nodiscard("No ignore return value!")]] +T run_file_script_and_get_value(const std::filesystem::path& path_file, + const std::string& var_name) +{ + PythonRunner pr; + auto val = pr.run_file_script_and_get_value(path_file, var_name); + return val; +} + +template +std::tuple convertBPTuple2StdTuple(const boost::python::tuple& pt) +{ + std::tuple t; + for (int i = 0; i < boost::python::len(pt); ++i) { + auto a = boost::python::extract(pt[i]); + std::tuple_cat(t, a); + } + + return t; +} + +} // of python_wrapper diff --git a/wolf/system/test/python.hpp b/wolf/system/test/python.hpp new file mode 100644 index 000000000..ea1f683c8 --- /dev/null +++ b/wolf/system/test/python.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include + +namespace python_testcase { + void apply_simple_script() + { + std::cout << "Test case #1 ...\n"; + python_wrapper::PythonRunner pr; // using object + pr.run_simple_script(); + python_wrapper::run_simple_script("print ('Hello, Boost.python')"); // using global function + std::cout << "Done\n"; + } + void apply_simple_script_from_file() + { + std::cout << "Test case #2\n"; + python_wrapper::PythonRunner pr; // using object + pr.run_simple_script_file("D:/script.py"); + python_wrapper::run_simple_script_from_file("D:/script.py"); // using global function + std::cout << "Done\n"; + } + + void apply_run_function_from_script_file_and_get_result() + { + std::cout << "Test case #3\n"; + python_wrapper::PythonRunner pr; // using object + auto val = pr.run_function_from_script_file_and_get_result("D:/simple_math.py", "return_42"); + std::cout << val << '\n'; + // python_wrapper::run_simple_script_from_file("D:/script.py"); // using global function + std::cout << "Done\n"; + } + void apply_call_function() + { + namespace bp = boost::python; + std::cout << "Test case #7\n"; + std::string script = + "def get_and_return(a, b, c): \n" + " return a, b, c \n" + "def greet():\n" + " print('Hello ...........')"; + python_wrapper::PythonRunner pr; // using object + pr.set_script(script); + auto ret = pr.call_function("get_and_return", {}, 7, 2, 3.14); + + if (bp::len(ret) != 3) + throw std::invalid_argument("bad"); + std::cout << bp::extract(ret[0]) << '\t' + << bp::extract(ret[1]) << '\t' + << bp::extract(ret[2]) << '\n'; + + std::cout << "Done\n"; + } + + // Note: add required test case here +} // of python_testcase