From 989ec66aaf9c7e330116b198463a0b3a33f56220 Mon Sep 17 00:00:00 2001 From: Ovidiu Mircescu Date: Mon, 14 Jan 2019 17:50:05 +0100 Subject: [PATCH 1/1] Initial version. --- CMakeLists.txt | 43 ++++++ README | 76 +++++++++ example/CMakeLists.txt | 43 ++++++ example/main.cxx | 53 +++++++ example/mymodule.py | 2 + src/CMakeLists.txt | 51 ++++++ src/Errors.cxx | 173 +++++++++++++++++++++ src/Errors.hxx | 78 ++++++++++ src/PyFunction.cxx | 64 ++++++++ src/PyFunction.hxx | 56 +++++++ src/PyPtr.cxx | 47 ++++++ src/PyPtr.hxx | 54 +++++++ src/Result.hxx | 121 +++++++++++++++ src/Test/CMakeLists.txt | 44 ++++++ src/Test/ConversionTest.cxx | 256 ++++++++++++++++++++++++++++++ src/Test/ConversionTest.hxx | 48 ++++++ src/Test/TestMain.cxx | 83 ++++++++++ src/Test/TestPy2cpp.py | 32 ++++ src/TypeConversions.cxx | 106 +++++++++++++ src/TypeConversions.hxx | 300 ++++++++++++++++++++++++++++++++++++ 20 files changed, 1730 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README create mode 100644 example/CMakeLists.txt create mode 100644 example/main.cxx create mode 100644 example/mymodule.py create mode 100644 src/CMakeLists.txt create mode 100644 src/Errors.cxx create mode 100644 src/Errors.hxx create mode 100644 src/PyFunction.cxx create mode 100644 src/PyFunction.hxx create mode 100644 src/PyPtr.cxx create mode 100644 src/PyPtr.hxx create mode 100644 src/Result.hxx create mode 100644 src/Test/CMakeLists.txt create mode 100644 src/Test/ConversionTest.cxx create mode 100644 src/Test/ConversionTest.hxx create mode 100644 src/Test/TestMain.cxx create mode 100644 src/Test/TestPy2cpp.py create mode 100644 src/TypeConversions.cxx create mode 100644 src/TypeConversions.hxx diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ba97e41 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,43 @@ +# Copyright (C) 2019 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +cmake_minimum_required(VERSION 2.6) +project(py2cpp) + + +SET(CONFIGURATION_ROOT_DIR $ENV{CONFIGURATION_ROOT_DIR} CACHE PATH "Path to the Salome CMake configuration files") +IF(EXISTS ${CONFIGURATION_ROOT_DIR}) + LIST(APPEND CMAKE_MODULE_PATH "${CONFIGURATION_ROOT_DIR}/cmake") + INCLUDE(SalomeMacros) +ELSE() + MESSAGE(FATAL_ERROR "We absolutely need the Salome CMake configuration files, please define CONFIGURATION_ROOT_DIR !" +) +ENDIF() + +OPTION(BUILD_EXAMPLE "Generate the example." OFF) + +ENABLE_TESTING() +FIND_PACKAGE(SalomePythonInterp REQUIRED) +FIND_PACKAGE(SalomePythonLibs REQUIRED) +SET(BUILD_SHARED_LIBS TRUE) + +SET (CMAKE_CXX_STANDARD 11) +ADD_SUBDIRECTORY(src) +IF(BUILD_EXAMPLE) + ADD_SUBDIRECTORY(example) +ENDIF(BUILD_EXAMPLE) diff --git a/README b/README new file mode 100644 index 0000000..c0beabf --- /dev/null +++ b/README @@ -0,0 +1,76 @@ +The py2cpp library was created in order to make easier the call of a python +function within c++ sources. It provides convertion functions to and from a +python object for some basic c++ types (int, double, std:: string and +collections for these types: std::vector, std::list, std::map). It is possible +to add your own convertion functions for your own types. + +Example of use +--------------- + +Consider you have the following python file "mymodule.py": + +________________________________________________________________________________ +def myfunction(a, b): + return "The result is", a/b +________________________________________________________________________________ + +You can call this function from c++ this way: + +________________________________________________________________________________ + #include "TypeConversions.hxx" + #include "Result.hxx" + #include "PyFunction.hxx" + ... + Py_Initialize(); + ... + std::string s; + double d; + py2cpp::PyFunction fn; + fn.load("mymodule", "myfunction"); + py2cpp::pyResult(s,d) = fn(1,2); + ... + std::cout << "String parameter from the python function:" << s << std::endl; + std::cout << "Double parameter from the python function:" << d << std::endl; + ... + Py_Finalize(); +________________________________________________________________________________ + +The full example which also deals with possible errors, can be this: + +________________________________________________________________________________ + #include "TypeConversions.hxx" + #include "Result.hxx" + #include "PyFunction.hxx" + + #include + + int main() + { + Py_Initialize(); + std::string s; + double d; + py2cpp::PyFunction fn; + fn.load("mymodule", "myfunction"); + if(!fn) + { + std::cerr << "Impossible to load myfunction from the module mymodule!"; + std::cerr << std::endl; + std::cerr << py2cpp::getLastPyError(); + } + else + { + try + { + py2cpp::pyResult(s,d) = fn(1, 2); + std::cout << "String parameter from the python function:" << s << std::endl; + std::cout << "Double parameter from the python function:" << d << std::endl; + } + catch(const py2cpp::Exception& err) + { + std::cerr << err.what(); + } + } + Py_Finalize(); + return 0; + } +________________________________________________________________________________ diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..56de32d --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,43 @@ +# Copyright (C) 2019 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +# additional include directories +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR}/../src + ${PYTHON_INCLUDE_DIR} +) + +ADD_DEFINITIONS( + ${PYTHON_DEFINITIONS} +) + +# libraries to link to +SET(_link_LIBRARIES + ${PYTHON_LIBRARIES} + py2cpp +) + +SET(Main_SOURCES + main.cxx +) + +ADD_EXECUTABLE(main ${Main_SOURCES}) +TARGET_LINK_LIBRARIES(main ${_link_LIBRARIES}) +INSTALL(TARGETS main RUNTIME DESTINATION example) +INSTALL(FILES mymodule.py DESTINATION example) diff --git a/example/main.cxx b/example/main.cxx new file mode 100644 index 0000000..e07ef3b --- /dev/null +++ b/example/main.cxx @@ -0,0 +1,53 @@ +// Copyright (C) 2019 EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#include "TypeConversions.hxx" +#include "Result.hxx" +#include "PyFunction.hxx" + +#include + +int main() +{ + Py_Initialize(); + std::string s; + double d; + py2cpp::PyFunction fn; + fn.load("mymodule", "myfunction"); + if(!fn) + { + std::cerr << "Impossible to load myfunction from the module mymodule!"; + std::cerr << std::endl; + std::cerr << py2cpp::getLastPyError(); + } + else + { + try + { + py2cpp::pyResult(s,d) = fn(1, 2); + std::cout << "String parameter from the python function:" << s << std::endl; + std::cout << "Double parameter from the python function:" << d << std::endl; + } + catch(const py2cpp::Exception& err) + { + std::cerr << err.what(); + } + } + Py_Finalize(); + return 0; +} diff --git a/example/mymodule.py b/example/mymodule.py new file mode 100644 index 0000000..e2eea8a --- /dev/null +++ b/example/mymodule.py @@ -0,0 +1,2 @@ +def myfunction(a, b): + return "The result is", a/b diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..e91516e --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,51 @@ +# Copyright (C) 2019 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +INCLUDE_DIRECTORIES( + ${PYTHON_INCLUDE_DIR} +) + +ADD_DEFINITIONS( + ${PYTHON_DEFINITIONS} +) + +SET(_link_LIBRARIES + ${PYTHON_LIBRARIES} +) + +SET(_py2cpp_sources + TypeConversions.cxx + PyFunction.cxx + Errors.cxx + PyPtr.cxx +) + +SET(_py2cpp_headers + TypeConversions.hxx + PyFunction.hxx + Errors.hxx + PyPtr.hxx + Result.hxx +) + +ADD_LIBRARY(py2cpp ${_py2cpp_sources}) +TARGET_LINK_LIBRARIES(py2cpp ${_link_LIBRARIES}) +INSTALL(TARGETS py2cpp LIBRARY DESTINATION lib) +INSTALL(FILES ${_py2cpp_headers} DESTINATION include/py2cpp) + +ADD_SUBDIRECTORY(Test) diff --git a/src/Errors.cxx b/src/Errors.cxx new file mode 100644 index 0000000..dfafa5c --- /dev/null +++ b/src/Errors.cxx @@ -0,0 +1,173 @@ +// Copyright (C) 2019 EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#include "Errors.hxx" +#include + +namespace py2cpp +{ +std::string getLastPyError() +{ + std::string result=""; + if (PyErr_Occurred()) + { + PyObject *ptype, *pvalue, *ptraceback; + PyObject *pystr, *module_name, *pyth_module, *pyth_func; + PyErr_Fetch(&ptype, &pvalue, &ptraceback); + PyErr_NormalizeException(&ptype, &pvalue, &ptraceback); + pystr = PyObject_Str(pvalue); + result = std::string(PyUnicode_AsUTF8(pystr)); + result += "\n"; + Py_DECREF(pystr); + + /* See if we can get a full traceback */ + if(ptraceback) + { + module_name = PyUnicode_FromString("traceback"); + pyth_module = PyImport_Import(module_name); + Py_DECREF(module_name); + if (pyth_module) + { + pyth_func = PyObject_GetAttrString(pyth_module, "format_exception"); + if (pyth_func && PyCallable_Check(pyth_func)) + { + PyObject *pyList; + pyList = PyObject_CallFunctionObjArgs(pyth_func, ptype, pvalue, ptraceback, NULL); + if(pyList) + { + int n = PyList_Size(pyList); + for(int i=0; i 80) + { + pyRepr = pyRepr.substr(0, 76); + pyRepr += "..."; + } + const std::string beginMessage = "Cannot convert the python object "; + const std::string midMessage = " to c++ type "; + int messageSize = pyRepr.size() + beginMessage.size() + midMessage.size(); + newMessage = beginMessage; + if(messageSize >= 80) + newMessage += "\n"; + newMessage += "<"; + newMessage += pyRepr; + newMessage += ">"; + if(messageSize > 80) + newMessage += "\n"; + newMessage += " to c++ type "; + newMessage += expectedType; + newMessage += ".\n"; + } + else + { + newMessage = "Cannont convert a NULL python object to c++ type "; + newMessage += expectedType; + newMessage += ".\n"; + } + addErrorMessage(newMessage); +} + +void ConversionCheck::addError(const ConversionCheck& err) +{ + _message += err.getMessage(); +} + +void ConversionCheck::addErrorMessage(const std::string& message) +{ + _message += message; +} + +void ConversionCheck::reset() +{ + _message = ""; +} + +Exception::Exception(const std::string& message) +: std::exception() +, _message(message) +{ +} + +const char* Exception::what() const noexcept +{ + return _message.c_str(); +} + +ConversionException::ConversionException(const std::string& message) +: Exception(message) +{ +} + +ExecutionException::ExecutionException(const std::string& message) +: Exception(message) +{ +} + +} diff --git a/src/Errors.hxx b/src/Errors.hxx new file mode 100644 index 0000000..56a2a1f --- /dev/null +++ b/src/Errors.hxx @@ -0,0 +1,78 @@ +// Copyright (C) 2019 EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#ifndef PY2CPP_ERRORS_HXX +#define PY2CPP_ERRORS_HXX + +#include +#include +#include + +namespace py2cpp +{ +/*! + * Retrieve the last python error and return it as a formated string, containing + * the traceback when possible. + * After the call of this function, the python error indicator is cleared. + * see PyErr_Fetch. + */ +std::string getLastPyError(); + +/*! + * ConversionCheck class gathers the errors within fromPy functions. + */ +class ConversionCheck +{ +public: + ConversionCheck(); + ConversionCheck(const std::string& error); + ~ConversionCheck(); + operator bool()const; //! true means no error + void addError(const std::string& expectedType, PyObject * obj); + void addError(const ConversionCheck& err); + void addErrorMessage(const std::string& message); + void reset(); //! Empty the error message. + const std::string& getMessage()const; +private: + std::string _message; +}; + +class Exception:public std::exception +{ +public: + Exception(const std::string& message); + virtual const char* what() const noexcept; +private: + std::string _message; +}; + +class ConversionException:public Exception +{ +public: + ConversionException(const std::string& message); +}; + +class ExecutionException:public Exception +{ +public: + ExecutionException(const std::string& message); +}; + +} + +#endif //PY2CPP_ERRORS_HXX diff --git a/src/PyFunction.cxx b/src/PyFunction.cxx new file mode 100644 index 0000000..f2b1c02 --- /dev/null +++ b/src/PyFunction.cxx @@ -0,0 +1,64 @@ +// Copyright (C) 2019 EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#include "PyFunction.hxx" +namespace py2cpp +{ +bool PyFunction::load(const std::string& module, const std::string& function) +{ + bool result = false; + reset(nullptr); + PyPtr moduleName(PyUnicode_FromString(module.c_str())); + PyPtr moduleObj(PyImport_Import(moduleName.get())); + if(moduleObj) + { + PyObject * newFunction = PyObject_GetAttrString(moduleObj.get(), + function.c_str()); + if(newFunction && PyCallable_Check(newFunction)) + { + reset(newFunction); + result = true; + } + } + return result; +} + +bool PyFunction::load(const PyPtr& obj, const std::string& function) +{ + return load(obj.get(), function); +} + +bool PyFunction::load(PyObject* obj, const std::string& function) +{ + bool result = false; + reset(nullptr); + if(obj && PyObject_HasAttrString(obj, function.c_str())) + { + PyObject * fn = PyObject_GetAttrString(obj, function.c_str()); + if(fn && PyCallable_Check(fn)) + { + reset(fn); + result = true; + } + else + Py_XDECREF(fn); + } + return result; +} + +} diff --git a/src/PyFunction.hxx b/src/PyFunction.hxx new file mode 100644 index 0000000..2a94118 --- /dev/null +++ b/src/PyFunction.hxx @@ -0,0 +1,56 @@ +// Copyright (C) 2019 EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#ifndef PY2CPP_FUNCTIONCALLS_HXX +#define PY2CPP_FUNCTIONCALLS_HXX +#include +#include +#include "TypeConversions.hxx" +namespace py2cpp +{ +class PyFunction : public PyPtr +{ +public: + /*! Load a callable object from a python module.*/ + bool load(const std::string& module, const std::string& function); + /*! Load a callable object member of a python object.*/ + bool load(const PyPtr& obj, const std::string& function); + bool load(PyObject* obj, const std::string& function); + + /*! + * The evaluation returns nullptr if the python function throws an exception. + * See PyObject_CallObject documentation. + * You can use getLastPyError in order to get the last python error. + */ + template + PyPtr operator()(const Ts&... args) + { + PyObject * result = nullptr; + PyObject * myFunc = get(); + if(myFunc && PyCallable_Check(myFunc)) + { + std::tuple tupleArgs(args...); + PyPtr pyArgs(toPy(tupleArgs)); + result = PyObject_CallObject(myFunc, pyArgs.get()); + } + return PyPtr(result); + } +}; +} + +#endif //PY2CPP_FUNCTIONCALLS_HXX diff --git a/src/PyPtr.cxx b/src/PyPtr.cxx new file mode 100644 index 0000000..3741c78 --- /dev/null +++ b/src/PyPtr.cxx @@ -0,0 +1,47 @@ +// Copyright (C) 2019 EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#include "PyPtr.hxx" +namespace py2cpp +{ + +PyPtr PyPtr::getAttr(const std::string& attribute)const +{ + PyObject* result = nullptr; + PyObject* thisObj = get(); + if(thisObj) + result = PyObject_GetAttrString(thisObj, attribute.c_str()); + + return PyPtr(result); +} + +std::string PyPtr::repr()const +{ + std::string result; + PyObject* thisObj = get(); + if(thisObj) + { + PyObject* pyResult = PyObject_Repr(thisObj); + if(pyResult && PyUnicode_Check(pyResult)) + result = PyUnicode_AsUTF8(pyResult); + Py_XDECREF(pyResult); + } + return result; +} + +} diff --git a/src/PyPtr.hxx b/src/PyPtr.hxx new file mode 100644 index 0000000..fdfd37b --- /dev/null +++ b/src/PyPtr.hxx @@ -0,0 +1,54 @@ +// Copyright (C) 2019 EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#ifndef PY2CPP_PYPTR_HXX +#define PY2CPP_PYPTR_HXX +#include +#include +#include + +namespace py2cpp +{ +class PyPtrDeleter +{ +public: + void operator()(PyObject * po){Py_DECREF(po);} +}; + +typedef std::unique_ptr _PyPtr; + +class PyPtr: public _PyPtr +{ +public: + using _PyPtr::_PyPtr; + PyPtr getAttr(const std::string& attribute)const; + std::string repr()const; +}; + +class AutoGIL +{ +public: + AutoGIL():_gstate(PyGILState_Ensure()) { } + ~AutoGIL() { PyGILState_Release(_gstate); } +private: + PyGILState_STATE _gstate; +}; + +} + +#endif // PY2CPP_PYPTR_HXX diff --git a/src/Result.hxx b/src/Result.hxx new file mode 100644 index 0000000..edb6f5e --- /dev/null +++ b/src/Result.hxx @@ -0,0 +1,121 @@ +// Copyright (C) 2019 EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#ifndef PY2CPP_RESULT_HXX +#define PY2CPP_RESULT_HXX +#include +#include +#include "TypeConversions.hxx" +#include "Errors.hxx" + +namespace py2cpp +{ + +template +class Result; + +/*! class Result is used by pyResult function for syntax sugar purpose. + * You can write this: + * double d; + * std::string str; + * py2cpp::PyFunction fn; + * fn.load("mymodule", "myfunction"); + * try + * py2cpp::pyResult(d, str) = fn(42); + * catch (const py2cpp::Exception& err) + * std::cerr << err.what(); + * + * Instead of that: + * double d; + * std::string str; + * py2cpp::PyFunction fn; + * fn.load("mymodule", "myfunction"); + * py2cpp::PyPtr fn_result = fn(42); + * if(fn_result) + * { + * std::tuple cpp_result(d, str); + * ConversionCheck check = fromPyPtr(fn_result, cpp_result); + * if(!check) + * std::cerr << err.getMessage(); + * } + * else + * std::cerr << py2cpp::getLastPyError(); + **/ +template<> +class Result<> +{ +public: + void operator=(PyObject * po) + { + if(!po) + throw ExecutionException(getLastPyError()); + } + void operator=(const PyPtr& po){*this = po.get();} +}; + +template +class Result +{ +public: + Result() = delete; + Result(T& v):_data(v){} + void operator=(PyObject *po) + { + if(!po) + throw ExecutionException(getLastPyError()); + ConversionCheck check; + check = fromPy(po, _data); + if(!check) + { + throw ConversionException(check.getMessage()); + } + } + void operator=(const PyPtr& po){ *this = po.get();} +private: + T& _data; +}; + +template +class Result +{ +public: + Result() = delete; + Result(Ts&...args):_data(args...){} + void operator=(PyObject *po) + { + if(!po) + throw ExecutionException(getLastPyError()); + ConversionCheck check; + check = fromPy(po, _data); + if(!check) + throw ConversionException(check.getMessage()); + } + void operator=(const PyPtr& po){ *this = po.get();}; +private: + std::tuple _data; +}; + +template +Result pyResult(Ts&... args) +{ + return Result(args...); +} + +} + +#endif //PY2CPP_RESULT_HXX diff --git a/src/Test/CMakeLists.txt b/src/Test/CMakeLists.txt new file mode 100644 index 0000000..7688d22 --- /dev/null +++ b/src/Test/CMakeLists.txt @@ -0,0 +1,44 @@ +# Copyright (C) 2019 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +FIND_PACKAGE(CppUnit REQUIRED) + +# additional include directories +INCLUDE_DIRECTORIES( + ${CPPUNIT_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +# libraries to link to +SET(_link_LIBRARIES + ${CPPUNIT_LIBRARIES} + ${PYTHON_LIBRARIES} + py2cpp +) + +SET(Test_SOURCES + ConversionTest.cxx +) + +ADD_EXECUTABLE(py2cppTest ${Test_SOURCES}) +TARGET_LINK_LIBRARIES(py2cppTest ${_link_LIBRARIES}) +ADD_TEST(Py2cppTest py2cppTest) +SET_TESTS_PROPERTIES(Py2cppTest PROPERTIES ENVIRONMENT +"PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:$ENV{PYTHONPATH}") + +INSTALL(TARGETS py2cppTest RUNTIME DESTINATION bin) diff --git a/src/Test/ConversionTest.cxx b/src/Test/ConversionTest.cxx new file mode 100644 index 0000000..ed93db5 --- /dev/null +++ b/src/Test/ConversionTest.cxx @@ -0,0 +1,256 @@ +// Copyright (C) 2019 EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#include "ConversionTest.hxx" + +#include "TypeConversions.hxx" +#include "Result.hxx" +#include "PyFunction.hxx" + +void ConversionTest::setUp() +{ + Py_Initialize(); +} + +void ConversionTest::tearDown() +{ + Py_Finalize(); +} + +void ConversionTest::cleanUp(){} + +void ConversionTest::basicTest() +{ + CPPUNIT_ASSERT(42==py2cpp::fromPyPtr(py2cpp::toPyPtr(42))); + CPPUNIT_ASSERT(4.2==py2cpp::fromPyPtr(py2cpp::toPyPtr(4.2))); + std::string toto; + toto = py2cpp::fromPyPtr(py2cpp::toPyPtr(std::string("toto"))); + CPPUNIT_ASSERT(toto == "toto"); + + auto v = std::make_tuple(4, 4.2); + py2cpp::PyPtr obj = py2cpp::toPyPtr(v); + + std::string s1; + double d; + int i; + std::tuple tup(i, d); + py2cpp::fromPyPtr(obj, tup); + CPPUNIT_ASSERT(4==std::get<0>(tup)); + CPPUNIT_ASSERT(4.2==std::get<1>(tup)); + int i2; + double d2; + py2cpp::pyResult(i2, d2) = obj; + CPPUNIT_ASSERT(4==i2); + CPPUNIT_ASSERT(4.2==d2); + py2cpp::pyResult(i2) = py2cpp::toPyPtr(42); + CPPUNIT_ASSERT(42 == i2); +} + +void ConversionTest::functionTest() +{ + int i; + double d; + std::string str; + py2cpp::PyFunction fn; + + CPPUNIT_ASSERT(fn.load("TestPy2cpp", "f1")); + CPPUNIT_ASSERT(fn); + py2cpp::pyResult(i, d) = fn(5, 4.8); + CPPUNIT_ASSERT(4==i); + CPPUNIT_ASSERT(6.7==d); + CPPUNIT_ASSERT(0==py2cpp::getLastPyError().size()); + + fn.load("TestPy2cpp", "f2"); + CPPUNIT_ASSERT(fn); + py2cpp::pyResult(str) = fn(); + CPPUNIT_ASSERT(str=="Hello world!"); + CPPUNIT_ASSERT(0==py2cpp::getLastPyError().size()); + + fn.load("TestPy2cpp", "f3"); + CPPUNIT_ASSERT(fn); + py2cpp::pyResult(d, str) = fn(7, 2, "Toto"); + CPPUNIT_ASSERT(3.5 == d); + CPPUNIT_ASSERT(str == "Toto is here!"); + CPPUNIT_ASSERT(0==py2cpp::getLastPyError().size()); + + CPPUNIT_ASSERT(!fn.load("nonexistent","nonexistent")); + CPPUNIT_ASSERT(!fn); + CPPUNIT_ASSERT(0 < py2cpp::getLastPyError().size()); +} + +void ConversionTest::vectorTest() +{ + std::vector v = {1.1, 2.2, 3.3, 4.4}; + py2cpp::PyPtr obj = py2cpp::toPyPtr(v); + py2cpp::fromPyPtr(obj, v); + + std::vector result; + py2cpp::PyFunction fn; + fn.load("TestPy2cpp", "add1ToList"); + py2cpp::pyResult(result) = fn(v); + + CPPUNIT_ASSERT(std::vector({2.1, 3.2, 4.3, 5.4}) == result); +} + +void ConversionTest::listTest() +{ + std::list v = {1.1, 2.2, 3.3, 4.4}; + py2cpp::PyPtr obj = py2cpp::toPyPtr(v); + py2cpp::fromPyPtr(obj, v); + + std::list result; + py2cpp::PyFunction fn; + fn.load("TestPy2cpp", "add1ToList"); + py2cpp::pyResult(result) = fn(v); + + CPPUNIT_ASSERT(std::list({2.1, 3.2, 4.3, 5.4}) == result); +} + +void ConversionTest::pyobjTest() +{ + py2cpp::PyFunction objcall; + objcall.load("TestPy2cpp","MyClass"); + CPPUNIT_ASSERT(objcall); + py2cpp::PyPtr obj = objcall(std::list({"Toto", "Titi", "Zaza"}), + std::vector({5.5, 2.7, 1.8, 9.2})); + py2cpp::PyFunction objFn1, objFn2, objFn3; + objFn1.load(obj, "namesTogether"); + objFn2.load(obj, "valuesSum"); + objFn3.load(obj, "addVal"); + std::string strResult; + double dbResult; + py2cpp::pyResult(strResult) = objFn1(); + py2cpp::pyResult(dbResult) = objFn2(); + CPPUNIT_ASSERT(strResult == "Toto-Titi-Zaza"); + CPPUNIT_ASSERT(19.2 == dbResult); + + objFn3(1); + py2cpp::pyResult(dbResult) = objFn2(); + CPPUNIT_ASSERT(23.2 == dbResult); + + py2cpp::PyFunction myObjectSize; + myObjectSize.load("TestPy2cpp", "myObjectSize"); + int i; + py2cpp::pyResult(i) = myObjectSize(obj); + CPPUNIT_ASSERT(7==i); + CPPUNIT_ASSERT(obj.getAttr("names").repr() == "['Toto', 'Titi', 'Zaza']"); + CPPUNIT_ASSERT(obj.getAttr("values").repr() == "[6.5, 3.7, 2.8, 10.2]"); + + objFn1.load("TestPy2cpp","newobj"); + py2cpp::PyPtr newobj; + py2cpp::pyResult(newobj, i) = objFn1(obj, std::vector({1,2,3})); + CPPUNIT_ASSERT(6==i); + CPPUNIT_ASSERT(newobj.getAttr("names").repr() == "['Toto', 'Titi', 'Zaza']"); + CPPUNIT_ASSERT(newobj.getAttr("values").repr() == "[1, 2, 3]"); +} + +void ConversionTest::pyErrorTest() +{ + double d; + std::string str; + py2cpp::PyFunction fn; + fn.load("TestPy2cpp", "f3"); + try + { + py2cpp::pyResult(d, str) = fn(7, 0, "problem"); + CPPUNIT_FAIL("Expected exception 'py2cpp::ExecutionException'!"); + } + catch (const py2cpp::ExecutionException& err) + { + str = err.what(); + CPPUNIT_ASSERT(str.find("ZeroDivisionError:") != std::string::npos) ; + } +} + +void ConversionTest::castErrorTest() +{ + int i; + double d; + std::string str; + std::vector vi; + std::vector vd; + std::vector vs; + std::map > msvd; + py2cpp::ConversionCheck check; + py2cpp::PyPtr obj; + + obj = py2cpp::toPyPtr(42); + check = py2cpp::fromPyPtr(obj, d); + CPPUNIT_ASSERT(check); + check = py2cpp::fromPyPtr(obj, vi); + CPPUNIT_ASSERT(check.getMessage() == +"Cannot convert the python object <42> to c++ type std::vector.\n"); + check = py2cpp::fromPyPtr(obj, str); + CPPUNIT_ASSERT(check.getMessage() == +"Cannot convert the python object <42> to c++ type std::string.\n"); + check = py2cpp::fromPyPtr(obj, msvd); + CPPUNIT_ASSERT(check.getMessage() == +"Cannot convert the python object <42> to c++ type std::map.\n"); + + obj = py2cpp::toPyPtr(std::vector({1,2,3,4,5,6})); + check = py2cpp::fromPyPtr(obj, i); + CPPUNIT_ASSERT(check.getMessage() == +"Cannot convert the python object <[1, 2, 3, 4, 5, 6]> to c++ type int.\n"); + check = py2cpp::fromPyPtr(obj, d); + CPPUNIT_ASSERT(check.getMessage() == +"Cannot convert the python object <[1, 2, 3, 4, 5, 6]> to c++ type double.\n"); + check = py2cpp::fromPyPtr(obj, vi); + CPPUNIT_ASSERT(check); + check = py2cpp::fromPyPtr(obj, vd); + CPPUNIT_ASSERT(check); + check = py2cpp::fromPyPtr(obj, str); + CPPUNIT_ASSERT(check.getMessage() == +"Cannot convert the python object <[1, 2, 3, 4, 5, 6]> to c++ type std::string.\n" + ); + + check = py2cpp::fromPyPtr(obj, vs); + CPPUNIT_ASSERT(check.getMessage() == +"Cannot convert the python object <1> to c++ type std::string.\n" +"Cannot convert the python object <[1, 2, 3, 4, 5, 6]> to c++ type std::vector.\n" + ); + check = py2cpp::fromPyPtr(obj, msvd); + CPPUNIT_ASSERT(check.getMessage() == +"Cannot convert the python object <[1, 2, 3, 4, 5, 6]> to c++ type std::map.\n"); + + std::map mid({ {1, 1.1},{2, 2.2},{3, 3.3},{4, 4.4},{5, 5.5}}); + obj = py2cpp::toPyPtr(mid); + check = py2cpp::fromPyPtr(obj, i); + // Cannot check the message because the string representation of the python + // dictionary may change. + CPPUNIT_ASSERT(!check); + check = py2cpp::fromPyPtr(obj, vd); + CPPUNIT_ASSERT(!check); + check = py2cpp::fromPyPtr(obj, msvd); + CPPUNIT_ASSERT(!check); + + std::map msd({ {"1", 1.1},{"2", 2.2},{"3", 3.3}, + {"4", 4.4},{"5", 5.5}}); + obj = py2cpp::toPyPtr(msd); + check = py2cpp::fromPyPtr(obj, msvd); + CPPUNIT_ASSERT(!check); + + std::map > msvs ; + msvs["a"] = std::vector({"azer", "aqwx"}); + msvs["b"] = std::vector({"bn,", "bvcxw"}); + obj = py2cpp::toPyPtr(msvs); + check = py2cpp::fromPyPtr(obj, msvd); + CPPUNIT_ASSERT(!check); +} + +CPPUNIT_TEST_SUITE_REGISTRATION( ConversionTest ); +#include "TestMain.cxx" diff --git a/src/Test/ConversionTest.hxx b/src/Test/ConversionTest.hxx new file mode 100644 index 0000000..7c11b39 --- /dev/null +++ b/src/Test/ConversionTest.hxx @@ -0,0 +1,48 @@ +// Copyright (C) 2019 EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#ifndef PY2CPP_CONVERSIONTEST_HXX +#define PY2CPP_CONVERSIONTEST_HXX + +#include + +class ConversionTest: public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ConversionTest); + CPPUNIT_TEST(basicTest); + CPPUNIT_TEST(functionTest); + CPPUNIT_TEST(vectorTest); + CPPUNIT_TEST(listTest); + CPPUNIT_TEST(pyobjTest); + CPPUNIT_TEST(pyErrorTest); + CPPUNIT_TEST(castErrorTest); + CPPUNIT_TEST_SUITE_END(); +public: + void setUp(); + void tearDown(); + void cleanUp(); + void basicTest(); + void functionTest(); + void vectorTest(); + void listTest(); + void pyobjTest(); + void pyErrorTest(); + void castErrorTest(); +}; + +#endif // PY2CPP_CONVERSIONTEST_HXX diff --git a/src/Test/TestMain.cxx b/src/Test/TestMain.cxx new file mode 100644 index 0000000..dcc31f3 --- /dev/null +++ b/src/Test/TestMain.cxx @@ -0,0 +1,83 @@ +// Copyright (C) 2019 EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// ============================================================================ +/*! + * Main program source for Unit Tests with cppunit package does not depend + * on actual tests, so we use the same for all partial unit tests. + */ +// ============================================================================ + +int main(int argc, char* argv[]) +{ + // --- Create the event manager and test controller + CPPUNIT_NS::TestResult controller; + + // --- Add a listener that collects test result + CPPUNIT_NS::TestResultCollector result; + controller.addListener( &result ); + + // --- Add a listener that print dots as test run. +#ifdef WIN32 + CPPUNIT_NS::TextTestProgressListener progress; +#else + CPPUNIT_NS::BriefTestProgressListener progress; +#endif + controller.addListener( &progress ); + + // --- Get the top level suite from the registry + + CPPUNIT_NS::Test *suite = + CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest(); + + // --- Adds the test to the list of test to run + + CPPUNIT_NS::TestRunner runner; + runner.addTest( suite ); + runner.run( controller); + + // --- Print test in a compiler compatible format. + std::ofstream testFile; + testFile.open("test.log", std::ios::out | std::ios::app); + testFile << "------ Idefix test log:" << std::endl; + CPPUNIT_NS::CompilerOutputter outputter( &result, testFile ); + outputter.write(); + + // --- Run the tests. + + bool wasSucessful = result.wasSuccessful(); + testFile.close(); + + // --- Return error code 1 if the one of test failed. + + return wasSucessful ? 0 : 1; +} diff --git a/src/Test/TestPy2cpp.py b/src/Test/TestPy2cpp.py new file mode 100644 index 0000000..2bf16b9 --- /dev/null +++ b/src/Test/TestPy2cpp.py @@ -0,0 +1,32 @@ +def f1(x, y): + return 4, 6.7 + +def f2(): + return "Hello world!" + +def f3(x, y, m): + return x / y, m+" is here!" + +def add1ToList(l): + return [x+1 for x in l] + +class MyClass: + def __init__(self, names, values): + self.names = names + self.values = values + + def namesTogether(self): + return "-".join(self.names) + + def valuesSum(self): + return sum(self.values) + + def addVal(self, v): + for i in range(len(self.values)): + self.values[i] += v + +def myObjectSize(obj): + return len(obj.names) + len(obj.values) + +def newobj(o, newVals): + return MyClass(o.names, newVals), sum(newVals) diff --git a/src/TypeConversions.cxx b/src/TypeConversions.cxx new file mode 100644 index 0000000..6d674c9 --- /dev/null +++ b/src/TypeConversions.cxx @@ -0,0 +1,106 @@ +// Copyright (C) 2019 EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#include "TypeConversions.hxx" + +namespace py2cpp +{ +ConversionCheck fromPy(PyObject * po, int& result) +{ + ConversionCheck check; + if(po && PyLong_Check(po)) + result = PyLong_AsLong(po); + else + check.addError("int", po); + return check; +} + +PyObject * toPy(int val) +{ + return PyLong_FromLong(val); +} + +ConversionCheck fromPy(PyObject * po, double& result) +{ + ConversionCheck check; + if(po && PyFloat_Check(po)) + result = PyFloat_AsDouble(po); + else if(po && PyLong_Check(po)) + { + int v = PyLong_AsLong(po); + result = v; + } + else + check.addError("double", po); + return check; +} + +PyObject * toPy(double val) +{ + return PyFloat_FromDouble(val); +} + +ConversionCheck fromPy(PyObject * po, std::string& result) +{ + ConversionCheck check; + if(po && PyUnicode_Check(po)) + result = PyUnicode_AsUTF8(po); + else + check.addError("std::string", po); + return check; +} + +PyObject * toPy(const std::string& val) +{ + return PyUnicode_FromString(val.c_str()); +} + +ConversionCheck fromPy( PyObject *po, PyObject *& result) +{ + result = po; + Py_XINCREF(result); + return ConversionCheck(); +} + +PyObject * toPy(PyObject * obj) +{ + Py_XINCREF(obj); + return obj; +} + +ConversionCheck fromPy( PyObject *po, PyPtr& result) +{ + Py_XINCREF(po); + result.reset(po); + return ConversionCheck(); +} + +PyObject * toPy(const PyPtr& obj) +{ + PyObject * result = obj.get(); + Py_XINCREF(result); + return result; +} + +template<> +PyPtr fromPy( PyObject *po) +{ + return PyPtr(po); +} + +} diff --git a/src/TypeConversions.hxx b/src/TypeConversions.hxx new file mode 100644 index 0000000..e035b2e --- /dev/null +++ b/src/TypeConversions.hxx @@ -0,0 +1,300 @@ +// Copyright (C) 2019 EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#ifndef PY2CPP_TYPECONVERSIONS_HXX +#define PY2CPP_TYPECONVERSIONS_HXX +#include + +#include +#include +#include +#include +#include +#include "PyPtr.hxx" +#include "Errors.hxx" + +namespace py2cpp +{ +/*! + * toPy functions return a new python object built from a c++ object. + * The conversion is always possible and it does not throw exceptions. + */ +PyObject * toPy(int); +PyObject * toPy(double); +PyObject * toPy(const std::string&); +PyObject * toPy(PyObject *); +PyObject * toPy(const PyPtr&); +template +PyObject * toPy(const std::vector& values); +template +PyObject * toPy(const std::list& values); +template +PyObject * toPy(const std::map& values); +template +PyObject * toPy(const std::tuple& vars ); + +/*! + * fromPy functions convert a python object to a c++ object. + * If the convertion fails, the ConversionCheck object returned contains the + * error message. No exception is thrown. + */ +ConversionCheck fromPy( PyObject *, int&); +ConversionCheck fromPy( PyObject *, double&); +ConversionCheck fromPy( PyObject *, std::string&); +ConversionCheck fromPy( PyObject *, PyObject *&); +ConversionCheck fromPy( PyObject *, PyPtr&); +template +ConversionCheck fromPy(PyObject * obj, std::tuple& vars); +template +ConversionCheck fromPy( PyObject *obj, std::vector& result); +template +ConversionCheck fromPy( PyObject *obj, std::list& result); +template +ConversionCheck fromPy( PyObject *obj, std::map& result); + +/*! This templated version of fromPy throws ConversionException if the + * conversion fails. + */ +template +T fromPy( PyObject *po); + +// These versions of fromPy and toPy use PyPtr instead of PyObject * +template +T fromPyPtr( const PyPtr& py); +template +ConversionCheck fromPyPtr( const PyPtr& py, T& var); +template +PyPtr toPyPtr(const T& v); +template<> +PyPtr fromPy( PyObject *po); + +//////////////////////////////////////////////////////////////////////////////// +// Template implementations +//////////////////////////////////////////////////////////////////////////////// + +// std::tuple +template +inline typename std::enable_if::type +addInPyTuple(PyObject * result, const std::tuple& vars ) +{ +} + +template +inline typename std::enable_if::type +addInPyTuple(PyObject * result, const std::tuple& vars ) +{ + PyObject * obj = toPy(std::get(vars)); + PyTuple_SetItem(result, I, obj); + addInPyTuple(result, vars); +} + +template +PyObject * toPy(const std::tuple& vars ) +{ + PyObject * result = PyTuple_New(sizeof...(Ts)); + addInPyTuple<0, Ts... >(result, vars); + return result; +} + +// std containers +template +PyObject * toPy(const std::vector& values) +{ + PyObject * result = PyList_New(values.size()); + for(std::size_t i = 0; i < values.size(); ++i) + PyList_SetItem(result, i, toPy(values[i])); + return result; +} + +template +PyObject * toPy(const std::list& values) +{ + PyObject * result = PyList_New(values.size()); + std::size_t i = 0; + for(const T& it : values) + { + PyList_SetItem(result, i, toPy(it)); + ++i; + } + return result; +} + +template +PyObject * toPy(const std::map& values) +{ + PyObject * result = PyDict_New(); + for(const auto& it: values) + { + PyPtr pyKey(toPy(it.first)); + PyPtr pyValue(toPy(it.second)); + PyDict_SetItem(result, pyKey.get(), pyValue.get()); + } + return result; +} + +template +inline typename std::enable_if::type +getFromPyTuple(PyObject * tup, std::tuple& vars ) +{ + return ConversionCheck(); +} + +template +inline typename std::enable_if::type +getFromPyTuple(PyObject * tup, std::tuple& vars ) +{ + ConversionCheck check; + PyObject * obj = PyTuple_GetItem(tup, I); + typedef typename std::tuple_element >::type T; + check.addError(fromPy(obj, std::get(vars))); + if(check) + check.addError(getFromPyTuple(tup, vars)); + return check; +} + +template +ConversionCheck fromPy(PyObject * obj, std::tuple& vars) +{ + ConversionCheck check; + if(obj) + { + if(PyTuple_Check(obj) && + PyTuple_Size(obj) == std::tuple_size >::value) + { + check.addError(getFromPyTuple<0, Ts...>(obj, vars)); + } + else + if(1 == std::tuple_size >::value) + { + check.addError(fromPy(obj, std::get<0>(vars))); + } + if(!check) + check.addError("std::tuple", obj); + } + else + check.addError("std::tuple", obj); + return check; +} + +template +ConversionCheck fromPy( PyObject *obj, std::vector& result) +{ + ConversionCheck check; + if(PyList_Check(obj)) + { + result.clear(); + std::size_t size = PyList_Size(obj); + result.resize(size); + for(std::size_t i=0; i < size && check; i++) + check.addError(fromPy(PyList_GetItem(obj, i), result[i])); + if(!check) + check.addError("std::vector", obj); + } + else + check.addError("std::vector", obj); + return check; +} + +template +ConversionCheck fromPy( PyObject *obj, std::list& result) +{ + ConversionCheck check; + if(PyList_Check(obj)) + { + result.clear(); + std::size_t size = PyList_Size(obj); + result.resize(size); //result will have "size" default constructed elements. + std::size_t i = 0; + for(T& it : result) + { + check.addError(fromPy(PyList_GetItem(obj, i), it)); + if(!check) + { + check.addError("std::list", obj); + break; + } + ++i; + } + } + else + check.addError("std::list", obj); + return check; +} + +template +ConversionCheck fromPy( PyObject *obj, std::map& result) +{ + ConversionCheck check; + if(PyDict_Check(obj)) + { + result.clear(); + PyPtr pyKeys(PyDict_Keys(obj)); + std::size_t size = PyList_Size(pyKeys.get()); + for(std::size_t i =0; i < size && check; ++i) + { + PyObject * curKey = PyList_GetItem(pyKeys.get(), i); + K key; + V value; + check.addError(fromPy(curKey, key)); + if(check) + check.addError(fromPy(PyDict_GetItem(obj, curKey), value)); + if(check) + result[key]=value; + else + check.addError("std::map", obj); + } + } + else + check.addError("std::map", obj); + return check; +} + +// PyPtr +template +T fromPyPtr( const PyPtr& py) +{ + T result; + fromPy(py.get(), result); + return result; +} + +template +ConversionCheck fromPyPtr( const PyPtr& py, T& var) +{ + return fromPy(py.get(), var); +} + +template +PyPtr toPyPtr(const T& v) +{ + return PyPtr(toPy(v)); +} + +template +T fromPy( PyObject *po) +{ + T result; + ConversionCheck check; + check = fromPy(po, result); + if(!check) + throw ConversionException(check.getMessage()); + return result; +} + +} +#endif //PY2CPP_TYPECONVERSIONS_HXX -- 2.39.2