Salome HOME
Initial version.
authorOvidiu Mircescu <ovidiu.mircescu@edf.fr>
Mon, 14 Jan 2019 16:50:05 +0000 (17:50 +0100)
committerOvidiu Mircescu <ovidiu.mircescu@edf.fr>
Mon, 14 Jan 2019 16:50:05 +0000 (17:50 +0100)
20 files changed:
CMakeLists.txt [new file with mode: 0644]
README [new file with mode: 0644]
example/CMakeLists.txt [new file with mode: 0644]
example/main.cxx [new file with mode: 0644]
example/mymodule.py [new file with mode: 0644]
src/CMakeLists.txt [new file with mode: 0644]
src/Errors.cxx [new file with mode: 0644]
src/Errors.hxx [new file with mode: 0644]
src/PyFunction.cxx [new file with mode: 0644]
src/PyFunction.hxx [new file with mode: 0644]
src/PyPtr.cxx [new file with mode: 0644]
src/PyPtr.hxx [new file with mode: 0644]
src/Result.hxx [new file with mode: 0644]
src/Test/CMakeLists.txt [new file with mode: 0644]
src/Test/ConversionTest.cxx [new file with mode: 0644]
src/Test/ConversionTest.hxx [new file with mode: 0644]
src/Test/TestMain.cxx [new file with mode: 0644]
src/Test/TestPy2cpp.py [new file with mode: 0644]
src/TypeConversions.cxx [new file with mode: 0644]
src/TypeConversions.hxx [new file with mode: 0644]

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ba97e41
--- /dev/null
@@ -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 (file)
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 <iostream>
+
+  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 (file)
index 0000000..56de32d
--- /dev/null
@@ -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 (file)
index 0000000..e07ef3b
--- /dev/null
@@ -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 <iostream>
+
+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 (file)
index 0000000..e2eea8a
--- /dev/null
@@ -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 (file)
index 0000000..e91516e
--- /dev/null
@@ -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 (file)
index 0000000..dfafa5c
--- /dev/null
@@ -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 <Python.h>
+
+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<n; i++)
+            {
+              pystr = PyList_GetItem(pyList,i);
+              result += std::string(PyUnicode_AsUTF8(pystr));
+            }
+            Py_DECREF(pyList);
+          }
+        }
+        Py_XDECREF(pyth_func);
+        Py_DECREF(pyth_module);
+      }
+    }
+    Py_XDECREF(ptype);
+    Py_XDECREF(pvalue);
+    Py_XDECREF(ptraceback);
+  }
+  return result;
+}
+
+ConversionCheck::ConversionCheck()
+: _message()
+{
+}
+
+ConversionCheck::ConversionCheck(const std::string& error)
+: _message(error)
+{
+}
+
+ConversionCheck::~ConversionCheck()
+{
+}
+
+ConversionCheck::operator bool()const
+{
+  return _message.size() == 0;
+}
+
+const std::string& ConversionCheck::getMessage()const
+{
+  return _message;
+}
+
+void ConversionCheck::addError(const std::string& expectedType, PyObject * obj)
+{
+  std::string newMessage;
+  if(obj)
+  {
+    std::string pyRepr;
+    PyObject* pyResult = PyObject_Repr(obj);
+    if(pyResult && PyUnicode_Check(pyResult))
+      pyRepr = PyUnicode_AsUTF8(pyResult);
+    else
+      pyRepr = "unknown representation";
+    if(pyRepr.size() > 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 (file)
index 0000000..56a2a1f
--- /dev/null
@@ -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 <string>
+#include <exception>
+#include <Python.h>
+
+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 (file)
index 0000000..f2b1c02
--- /dev/null
@@ -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 (file)
index 0000000..2a94118
--- /dev/null
@@ -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 <Python.h>
+#include <tuple>
+#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 <class ...Ts>
+  PyPtr operator()(const Ts&... args)
+  {
+    PyObject * result = nullptr;
+    PyObject * myFunc = get();
+    if(myFunc && PyCallable_Check(myFunc))
+    {
+      std::tuple<const Ts&...> 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 (file)
index 0000000..3741c78
--- /dev/null
@@ -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 (file)
index 0000000..fdfd37b
--- /dev/null
@@ -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 <Python.h>
+#include <memory>
+#include <string>
+
+namespace py2cpp
+{
+class PyPtrDeleter
+{
+public:
+  void operator()(PyObject * po){Py_DECREF(po);}
+};
+
+typedef std::unique_ptr<PyObject, PyPtrDeleter> _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 (file)
index 0000000..edb6f5e
--- /dev/null
@@ -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 <Python.h>
+#include <tuple>
+#include "TypeConversions.hxx"
+#include "Errors.hxx"
+
+namespace py2cpp
+{
+
+template<class ...Ts>
+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<double&, std::string&> 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 T>
+class Result<T>
+{
+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 ...Ts>
+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<Ts&...> _data;
+};
+
+template<class ...Ts>
+Result<Ts...> pyResult(Ts&... args)
+{
+  return Result<Ts...>(args...);
+}
+
+}
+
+#endif //PY2CPP_RESULT_HXX
diff --git a/src/Test/CMakeLists.txt b/src/Test/CMakeLists.txt
new file mode 100644 (file)
index 0000000..7688d22
--- /dev/null
@@ -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 (file)
index 0000000..ed93db5
--- /dev/null
@@ -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<int>(py2cpp::toPyPtr(42)));
+  CPPUNIT_ASSERT(4.2==py2cpp::fromPyPtr<double>(py2cpp::toPyPtr(4.2)));
+  std::string toto;
+  toto = py2cpp::fromPyPtr<std::string>(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<int&, double&> 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<double> v = {1.1, 2.2, 3.3, 4.4};
+  py2cpp::PyPtr obj = py2cpp::toPyPtr(v);
+  py2cpp::fromPyPtr(obj, v);
+
+  std::vector<double> result;
+  py2cpp::PyFunction fn;
+  fn.load("TestPy2cpp", "add1ToList");
+  py2cpp::pyResult(result) = fn(v);
+
+  CPPUNIT_ASSERT(std::vector<double>({2.1, 3.2, 4.3, 5.4}) == result);
+}
+
+void ConversionTest::listTest()
+{
+  std::list<double> v = {1.1, 2.2, 3.3, 4.4};
+  py2cpp::PyPtr obj = py2cpp::toPyPtr(v);
+  py2cpp::fromPyPtr(obj, v);
+
+  std::list<double> result;
+  py2cpp::PyFunction fn;
+  fn.load("TestPy2cpp", "add1ToList");
+  py2cpp::pyResult(result) = fn(v);
+
+  CPPUNIT_ASSERT(std::list<double>({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<std::string>({"Toto", "Titi", "Zaza"}),
+                              std::vector<double>({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<int>({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<int> vi;
+  std::vector<double> vd;
+  std::vector<std::string> vs;
+  std::map<std::string, std::vector<double> > 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<int>({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<int, double> 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<std::string, double> 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<std::string, std::vector<std::string> > msvs ;
+  msvs["a"] = std::vector<std::string>({"azer", "aqwx"});
+  msvs["b"] = std::vector<std::string>({"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 (file)
index 0000000..7c11b39
--- /dev/null
@@ -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 <cppunit/extensions/HelperMacros.h>
+
+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 (file)
index 0000000..dcc31f3
--- /dev/null
@@ -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 <cppunit/CompilerOutputter.h>
+#include <cppunit/TestResult.h>
+#include <cppunit/TestResultCollector.h>
+#include <cppunit/TextTestProgressListener.h>
+#include <cppunit/BriefTestProgressListener.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <cppunit/TestRunner.h>
+#include <cppunit/TextTestRunner.h>
+#include <stdexcept>
+
+#include <iostream>
+#include <fstream>
+#include <stdlib.h>
+
+// ============================================================================
+/*!
+ *  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 (file)
index 0000000..2bf16b9
--- /dev/null
@@ -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 (file)
index 0000000..6d674c9
--- /dev/null
@@ -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<PyPtr>( PyObject *po)
+{
+  return PyPtr(po);
+}
+
+}
diff --git a/src/TypeConversions.hxx b/src/TypeConversions.hxx
new file mode 100644 (file)
index 0000000..e035b2e
--- /dev/null
@@ -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 <Python.h>
+
+#include <list>
+#include <vector>
+#include <map>
+#include <string>
+#include <tuple>
+#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 <class T>
+PyObject * toPy(const std::vector<T>& values);
+template <class T>
+PyObject * toPy(const std::list<T>& values);
+template <class K, class V>
+PyObject * toPy(const std::map<K, V>& values);
+template<class ...Ts>
+PyObject * toPy(const std::tuple<Ts...>& 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<class ...Ts>
+ConversionCheck fromPy(PyObject * obj, std::tuple<Ts&...>& vars);
+template <class T>
+ConversionCheck fromPy( PyObject *obj, std::vector<T>& result);
+template <class T>
+ConversionCheck fromPy( PyObject *obj, std::list<T>& result);
+template <class K, class V>
+ConversionCheck fromPy( PyObject *obj, std::map<K, V>& result);
+
+/*! This templated version  of fromPy throws ConversionException if the
+ * conversion fails.
+ */
+template<class T>
+T fromPy( PyObject *po);
+
+// These versions of fromPy and toPy use PyPtr instead of PyObject *
+template<class T>
+T fromPyPtr( const PyPtr& py);
+template<class T>
+ConversionCheck fromPyPtr( const PyPtr& py, T& var);
+template<class T>
+PyPtr toPyPtr(const T& v);
+template<>
+PyPtr fromPy<PyPtr>( PyObject *po);
+
+////////////////////////////////////////////////////////////////////////////////
+// Template implementations
+////////////////////////////////////////////////////////////////////////////////
+
+// std::tuple
+template<std::size_t I = 0, class ...Ts>
+inline typename std::enable_if<I == sizeof...(Ts), void>::type
+addInPyTuple(PyObject * result, const std::tuple<Ts...>& vars )
+{
+}
+
+template<std::size_t I = 0, class ...Ts>
+inline typename std::enable_if<I < sizeof...(Ts), void>::type
+addInPyTuple(PyObject * result, const std::tuple<Ts...>& vars )
+{
+  PyObject * obj = toPy(std::get<I>(vars));
+  PyTuple_SetItem(result, I, obj);
+  addInPyTuple<I+1, Ts... >(result, vars);
+}
+
+template<class ...Ts>
+PyObject * toPy(const std::tuple<Ts...>& vars )
+{
+  PyObject * result = PyTuple_New(sizeof...(Ts));
+  addInPyTuple<0, Ts... >(result, vars);
+  return result;
+}
+
+// std containers
+template <class T>
+PyObject * toPy(const std::vector<T>& 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 <class T>
+PyObject * toPy(const std::list<T>& 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 <class K, class V>
+PyObject * toPy(const std::map<K, V>& 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<std::size_t I = 0, class ...Ts>
+inline typename std::enable_if<I == sizeof...(Ts), ConversionCheck>::type
+getFromPyTuple(PyObject * tup, std::tuple<Ts&...>& vars )
+{
+  return ConversionCheck();
+}
+
+template<std::size_t I = 0, class ...Ts>
+inline typename std::enable_if<I < sizeof...(Ts), ConversionCheck>::type
+getFromPyTuple(PyObject * tup, std::tuple<Ts&...>& vars )
+{
+  ConversionCheck check;
+  PyObject * obj = PyTuple_GetItem(tup, I);
+  typedef typename std::tuple_element<I, std::tuple<Ts...> >::type T;
+  check.addError(fromPy(obj, std::get<I>(vars)));
+  if(check)
+    check.addError(getFromPyTuple<I+1, Ts...>(tup, vars));
+  return check;
+}
+
+template<class ...Ts>
+ConversionCheck fromPy(PyObject * obj, std::tuple<Ts&...>& vars)
+{
+  ConversionCheck check;
+  if(obj)
+  {
+    if(PyTuple_Check(obj) && 
+      PyTuple_Size(obj) == std::tuple_size<std::tuple<Ts&...> >::value)
+    {
+      check.addError(getFromPyTuple<0, Ts...>(obj, vars));
+    }
+    else
+      if(1 == std::tuple_size<std::tuple<Ts&...> >::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 <class T>
+ConversionCheck fromPy( PyObject *obj, std::vector<T>& 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 <class T>
+ConversionCheck fromPy( PyObject *obj, std::list<T>& 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 <class K, class V>
+ConversionCheck fromPy( PyObject *obj, std::map<K, V>& 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<class T>
+T fromPyPtr( const PyPtr& py)
+{
+  T result;
+  fromPy(py.get(), result);
+  return result;
+}
+
+template<class T>
+ConversionCheck fromPyPtr( const PyPtr& py, T& var)
+{
+  return fromPy(py.get(), var);
+}
+
+template<class T>
+PyPtr toPyPtr(const T& v)
+{
+  return PyPtr(toPy(v));
+}
+
+template<class T>
+T fromPy( PyObject *po)
+{
+  T result;
+  ConversionCheck check;
+  check = fromPy(po, result);
+  if(!check)
+    throw ConversionException(check.getMessage());
+  return result;
+}
+
+}
+#endif //PY2CPP_TYPECONVERSIONS_HXX