From 938b2c42ca4c006015b03d3b361cd930b0b768ec Mon Sep 17 00:00:00 2001 From: Ovidiu Mircescu Date: Mon, 4 Feb 2019 17:42:41 +0100 Subject: [PATCH] Various improvements. Add convertions for unsigned, bool and char*. Add setAttr function for convenience. Use exceptions for the error management of some functions. Create a global header py2cpp.hxx. --- src/CMakeLists.txt | 1 + src/Errors.cxx | 5 +++++ src/Errors.hxx | 6 ++++++ src/PyFunction.cxx | 35 ++++++++++++++++++++++++++++++ src/PyFunction.hxx | 19 +++++++++++++--- src/PyPtr.cxx | 43 ++++++++++++++++++++++++++++++++++--- src/PyPtr.hxx | 1 + src/Test/ConversionTest.cxx | 18 ++++++++++++---- src/TypeConversions.cxx | 35 ++++++++++++++++++++++++++++++ src/TypeConversions.hxx | 5 +++++ src/py2cpp.hxx | 26 ++++++++++++++++++++++ 11 files changed, 184 insertions(+), 10 deletions(-) create mode 100644 src/py2cpp.hxx diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0b05a4c..fbd6d76 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -41,6 +41,7 @@ SET(_py2cpp_headers Errors.hxx PyPtr.hxx Result.hxx + py2cpp.hxx ) ADD_LIBRARY(py2cpp ${_py2cpp_sources}) diff --git a/src/Errors.cxx b/src/Errors.cxx index d6b0d59..eb80814 100644 --- a/src/Errors.cxx +++ b/src/Errors.cxx @@ -171,4 +171,9 @@ ExecutionException::ExecutionException(const std::string& message) { } +AttributeException::AttributeException(const std::string& message) +: Exception(message) +{ +} + } diff --git a/src/Errors.hxx b/src/Errors.hxx index 56a2a1f..bf709c3 100644 --- a/src/Errors.hxx +++ b/src/Errors.hxx @@ -73,6 +73,12 @@ public: ExecutionException(const std::string& message); }; +class AttributeException:public Exception +{ +public: + AttributeException(const std::string& message); +}; + } #endif //PY2CPP_ERRORS_HXX diff --git a/src/PyFunction.cxx b/src/PyFunction.cxx index f2b1c02..8742734 100644 --- a/src/PyFunction.cxx +++ b/src/PyFunction.cxx @@ -61,4 +61,39 @@ bool PyFunction::load(PyObject* obj, const std::string& function) return result; } +void PyFunction::loadExp(const std::string& module, const std::string& function) +{ + if(!load(module, function)) + { + std::string errorMessage = "Failed to load function '"; + errorMessage += function; + errorMessage += "' from module '"; + errorMessage += module; + errorMessage += "'\n"; + throw ExecutionException(errorMessage+getLastPyError()); + } +} + +void PyFunction::loadExp(const PyPtr& obj, const std::string& function) +{ + if(!load(obj, function)) + { + std::string errorMessage = "Failed to load function '"; + errorMessage += function; + errorMessage += "' from a python object.\n"; + throw ExecutionException(errorMessage+getLastPyError()); + } +} + +void PyFunction::loadExp(PyObject* obj, const std::string& function) +{ + if(!load(obj, function)) + { + std::string errorMessage = "Failed to load function '"; + errorMessage += function; + errorMessage += "' from a python object.\n"; + throw ExecutionException(errorMessage+getLastPyError()); + } +} + } diff --git a/src/PyFunction.hxx b/src/PyFunction.hxx index 2a94118..65cb052 100644 --- a/src/PyFunction.hxx +++ b/src/PyFunction.hxx @@ -21,6 +21,8 @@ #include #include #include "TypeConversions.hxx" +#include "Errors.hxx" + namespace py2cpp { class PyFunction : public PyPtr @@ -32,6 +34,12 @@ public: bool load(const PyPtr& obj, const std::string& function); bool load(PyObject* obj, const std::string& function); + // The following versions of the functions throw ExecutionException in case of + // failure. + void loadExp(const std::string& module, const std::string& function); + void loadExp(const PyPtr& obj, const std::string& function); + void loadExp(PyObject* obj, const std::string& function); + /*! * The evaluation returns nullptr if the python function throws an exception. * See PyObject_CallObject documentation. @@ -40,15 +48,20 @@ public: template PyPtr operator()(const Ts&... args) { - PyObject * result = nullptr; + PyPtr result; PyObject * myFunc = get(); if(myFunc && PyCallable_Check(myFunc)) { std::tuple tupleArgs(args...); PyPtr pyArgs(toPy(tupleArgs)); - result = PyObject_CallObject(myFunc, pyArgs.get()); + result.reset(PyObject_CallObject(myFunc, pyArgs.get())); + } + if(!result) + { + std::string errorMessage = "Failed to execute python function.\n"; + throw ExecutionException(errorMessage+getLastPyError()); } - return PyPtr(result); + return result; } }; } diff --git a/src/PyPtr.cxx b/src/PyPtr.cxx index 3741c78..c5624bc 100644 --- a/src/PyPtr.cxx +++ b/src/PyPtr.cxx @@ -17,6 +17,8 @@ // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com // #include "PyPtr.hxx" +#include "Errors.hxx" + namespace py2cpp { @@ -24,12 +26,47 @@ PyPtr PyPtr::getAttr(const std::string& attribute)const { PyObject* result = nullptr; PyObject* thisObj = get(); - if(thisObj) - result = PyObject_GetAttrString(thisObj, attribute.c_str()); - + if(nullptr == thisObj) + { + std::string message = "Cannot get attribute "; + message += attribute; + message += " on a NULL object.\n"; + throw AttributeException(message); + } + + if(!PyObject_HasAttrString(thisObj, attribute.c_str())) + { + std::string message = "Attribute "; + message += attribute; + message += " does not exist.\n"; + throw AttributeException(message); + } + + result = PyObject_GetAttrString(thisObj, attribute.c_str()); return PyPtr(result); } +void PyPtr::setAttr(const std::string& attribute, const PyPtr& value)const +{ + PyObject* thisObj = get(); + if(nullptr == thisObj) + { + std::string message = "Cannot set attribute "; + message += attribute; + message += " on a NULL object.\n"; + throw AttributeException(message); + } + + if(0 > PyObject_SetAttrString(thisObj, attribute.c_str(), value.get())) + { + std::string message = "Failed to set attribute "; + message += attribute; + message += ".\n"; + message += getLastPyError(); + throw AttributeException(message); + } +} + std::string PyPtr::repr()const { std::string result; diff --git a/src/PyPtr.hxx b/src/PyPtr.hxx index fdfd37b..9c794f6 100644 --- a/src/PyPtr.hxx +++ b/src/PyPtr.hxx @@ -37,6 +37,7 @@ class PyPtr: public _PyPtr public: using _PyPtr::_PyPtr; PyPtr getAttr(const std::string& attribute)const; + void setAttr(const std::string& attribute, const PyPtr& value)const; std::string repr()const; }; diff --git a/src/Test/ConversionTest.cxx b/src/Test/ConversionTest.cxx index ed93db5..e4e704b 100644 --- a/src/Test/ConversionTest.cxx +++ b/src/Test/ConversionTest.cxx @@ -17,10 +17,7 @@ // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com // #include "ConversionTest.hxx" - -#include "TypeConversions.hxx" -#include "Result.hxx" -#include "PyFunction.hxx" +#include "py2cpp.hxx" void ConversionTest::setUp() { @@ -38,6 +35,8 @@ void ConversionTest::basicTest() { CPPUNIT_ASSERT(42==py2cpp::fromPyPtr(py2cpp::toPyPtr(42))); CPPUNIT_ASSERT(4.2==py2cpp::fromPyPtr(py2cpp::toPyPtr(4.2))); + CPPUNIT_ASSERT(py2cpp::fromPyPtr(py2cpp::toPyPtr(true))); + CPPUNIT_ASSERT(!py2cpp::fromPyPtr(py2cpp::toPyPtr(false))); std::string toto; toto = py2cpp::fromPyPtr(py2cpp::toPyPtr(std::string("toto"))); CPPUNIT_ASSERT(toto == "toto"); @@ -165,6 +164,17 @@ void ConversionTest::pyErrorTest() std::string str; py2cpp::PyFunction fn; fn.load("TestPy2cpp", "f3"); + try + { + 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) ; + } + try { py2cpp::pyResult(d, str) = fn(7, 0, "problem"); diff --git a/src/TypeConversions.cxx b/src/TypeConversions.cxx index 6d674c9..d50b3a8 100644 --- a/src/TypeConversions.cxx +++ b/src/TypeConversions.cxx @@ -35,6 +35,36 @@ PyObject * toPy(int val) return PyLong_FromLong(val); } +ConversionCheck fromPy(PyObject * po, unsigned int& result) +{ + ConversionCheck check; + if(po && PyLong_Check(po)) + result = PyLong_AsUnsignedLong(po); + else + check.addError("int", po); + return check; +} + +PyObject * toPy(unsigned int val) +{ + return PyLong_FromUnsignedLong(val); +} + +ConversionCheck fromPy(PyObject * po, bool& result) +{ + ConversionCheck check; + if(po && PyBool_Check(po)) + result = (Py_True == po); + else + check.addError("bool", po); + return check; +} + +PyObject * toPy(bool val) +{ + return PyBool_FromLong(val); +} + ConversionCheck fromPy(PyObject * po, double& result) { ConversionCheck check; @@ -70,6 +100,11 @@ PyObject * toPy(const std::string& val) return PyUnicode_FromString(val.c_str()); } +PyObject * toPy(const char* val) +{ + return PyUnicode_FromString(val); +} + ConversionCheck fromPy( PyObject *po, PyObject *& result) { result = po; diff --git a/src/TypeConversions.hxx b/src/TypeConversions.hxx index e035b2e..9e04d57 100644 --- a/src/TypeConversions.hxx +++ b/src/TypeConversions.hxx @@ -35,8 +35,11 @@ namespace py2cpp * The conversion is always possible and it does not throw exceptions. */ PyObject * toPy(int); +PyObject * toPy(unsigned int); +PyObject * toPy(bool); PyObject * toPy(double); PyObject * toPy(const std::string&); +PyObject * toPy(const char*); PyObject * toPy(PyObject *); PyObject * toPy(const PyPtr&); template @@ -54,6 +57,8 @@ PyObject * toPy(const std::tuple& vars ); * error message. No exception is thrown. */ ConversionCheck fromPy( PyObject *, int&); +ConversionCheck fromPy( PyObject *, unsigned int&); +ConversionCheck fromPy( PyObject *, bool&); ConversionCheck fromPy( PyObject *, double&); ConversionCheck fromPy( PyObject *, std::string&); ConversionCheck fromPy( PyObject *, PyObject *&); diff --git a/src/py2cpp.hxx b/src/py2cpp.hxx new file mode 100644 index 0000000..4eb7e4f --- /dev/null +++ b/src/py2cpp.hxx @@ -0,0 +1,26 @@ + +// 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_PY2CPP_HXX +#define PY2CPP_PY2CPP_HXX + +#include "PyFunction.hxx" +#include "Result.hxx" + +#endif //PY2CPP_PY2CPP_HXX -- 2.39.2