I have the following simple hierarchy of two C++ exceptions:
class LIB_EXP ClusterException : public std::exception { public: ClusterException() { } ClusterException(const std::string& what) { init(what); } virtual const char* what() const throw() { return what_.c_str(); } virtual ~ClusterException() throw() {} virtual ClusterException* clone() { return new ClusterException(*this); } protected: void init(const std::string& what) { what_ = what; } private: std::string what_; }; class LIB_EXP ClusterExecutionException : public ClusterException { public: ClusterExecutionException(const std::string& jsonResponse); std::string getErrorType() const throw() { return errorType_; } std::string getClusterResponse() const throw() { return clusterResponse_; } virtual ~ClusterExecutionException() throw() {} virtual ClusterExecutionException* clone() { return new ClusterExecutionException(*this); } private: std::string errorType_; std::string clusterResponse_; };
I then export them to Python with Boost-Python as follows. Note my use of the bases
to make sure that the inheritance relationship is preserved in the translation:
class_<ClusterException> clusterException("ClusterException", no_init); clusterException.add_property("message", &ClusterException::what); clusterExceptionType = clusterException.ptr(); register_exception_translator<ClusterException>(&translateClusterException); class_<ClusterExecutionException, bases<ClusterException> > clusterExecutionException("ClusterExecutionException", no_init); clusterExecutionException.add_property("message", &ClusterExecutionException::what) .add_property("errorType", &ClusterExecutionException::getErrorType) .add_property("clusterResponse", &ClusterExecutionException::getClusterResponse); clusterExecutionExceptionType = clusterExecutionException.ptr(); register_exception_translator<ClusterExecutionException>(&translateClusterExecutionException);
Then the exception translation method:
static PyObject *clusterExceptionType = NULL; static void translateClusterException(ClusterException const &exception) { assert(clusterExceptionType != NULL); boost::python::object pythonExceptionInstance(exception); PyErr_SetObject(clusterExceptionType, pythonExceptionInstance.ptr()); } static PyObject *clusterExecutionExceptionType = NULL; static void translateClusterExecutionException(ClusterExecutionException const &exception) { assert(clusterExecutionExceptionType != NULL); boost::python::object pythonExceptionInstance(exception); PyErr_SetObject(clusterExecutionExceptionType, pythonExceptionInstance.ptr()); }
I created the following test C++ function that throws the exceptions:
static void boomTest(int exCase) { switch (exCase) { case 0: throw ClusterException("Connection to server failed"); break; case 1: throw ClusterExecutionException("Error X while executing in the cluster"); break; default: throw std::runtime_error("Unknown exception type"); } }
Finally the Python test code that calls my C++ boomTest
:
import cluster reload(cluster) from cluster import ClusterException, ClusterExecutionException def test_exception(exCase): try: cluster.boomTest(exCase) except ClusterException as ex: print 'Success! ClusterException gracefully handled:' \ '\n message="%s"' % ex.message except ClusterExecutionException as ex: print 'Success! ClusterExecutionException gracefully handled:' \ '\n message="%s"' \ '\n errorType="%s"' \ '\n clusterResponse="%s"' % (ex.message, ex.errorType, ex.clusterResponse) except: print 'Caught unknown exception: %s "%s"' % (sys.exc_info()[0], sys.exc_info()[1]) def main(): print '\n************************ throwing ClusterException ***********************************************************************' test_exception(0) print '\n************************ throwing ClusterExecutionException **************************************************************' test_exception(1) print '\n************************ throwing std::runtime_error *********************************************************************' test_exception(2) if __name__ == "__main__": main()
Up to here it all works. However if I remove the ClusterExecutionException
catch handler from Python then this exception will be caught and fallback to an unknown exception instead of being caught as its base ClusterException
.
I have tried in Boost-Python while registering the exception translation of ClusterExecutionException
to register it as its base ClusterException
and then it gets caught "polymorphically" but then it won't be caught as a ClusterExecutionException
. How can make it so that ClusterExecutionException
gets caught as both ClusterException
and ClusterExecutionException
? I have tried of course registering this ClusterExecutionException
exception as both ClusterException
and ClusterExecutionException
but it follows a last wins strategy, and only one works not both.
Is there any other way to solve this?
UPDATE 1: The wholy grail of this problem is to find out on the C++ side the type of the except
Python statement e.g. except ClusterException as ex:
which is unknown once inside the C++ side. The exception translate by Boost.Python will call the translate function that corresponds to the dynamic type of the exception and the Python catch static type is not known.
UPDATE 2: As suggested changing the Python code to the following i.e. adding print(type(ex).__bases__)
gives:
def test_exception(exCase): try: cluster.boomTest(exCase) except ClusterException as ex: print(type(ex).__bases__) print 'Success! ClusterException gracefully handled:' \ '\n message="%s"' % ex.message except ClusterExecutionException as ex: print(type(ex).__bases__) print 'Success! ClusterExecutionException gracefully handled:' \ '\n message="%s"' \ '\n errorType="%s"' \ '\n clusterResponse="%s"' % (ex.message, ex.errorType, ex.clusterResponse) except: print 'Caught unknown exception: %s "%s"' % (sys.exc_info()[0], sys.exc_info()[1])
and the output:
************************ throwing ClusterException *********************************************************************** (<type 'Boost.Python.instance'>,) Success! ClusterException gracefully handled: message="Connection to server failed" ************************ throwing ClusterExecutionException ************************************************************** (<class 'cluster.ClusterException'>,) Success! ClusterExecutionException gracefully handled: message="Error X while executing in the cluster" errorType="LifeCycleException" clusterResponse="{ "resultStatus": "Error", "errorType": "LifeCycleException", "errorMessage": "Error X while executing in the cluster" }"
Meaning that the inheritance relationship is "seen" from Python. But the polymorphic handling still doesn't work.
UPDATE 3 This is the output of running VS dumpbin.exe
:
The command I used is:
dumpbin.exe /EXPORTS /SYMBOLS C:\ClusterDK\x64\Debug\ClusterDK.dll > c:\temp\dumpbin.out
and the relevant parts of the output:
Microsoft (R) COFF/PE Dumper Version 11.00.50727.1 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file C:\ClusterDK\x64\Debug\ClusterDK.dll File Type: DLL Section contains the following exports for ClusterDK.dll 00000000 characteristics 5A1689DA time date stamp Thu Nov 23 09:42:02 2017 0.00 version 1 ordinal base 78 number of functions 78 number of names ordinal hint RVA name 8 7 00004485 ??0ClusterException@cluster@@QEAA@AEBV01@@Z = @ILT+13440(??0ClusterException@cluster@@QEAA@AEBV01@@Z) 9 8 00001659 ??0ClusterException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z = @ILT+1620(??0ClusterException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) 10 9 00001F1E ??0ClusterException@cluster@@QEAA@XZ = @ILT+3865(??0ClusterException@cluster@@QEAA@XZ) 11 A 00004D4F ??0ClusterExecutionException@cluster@@QEAA@AEBV01@@Z = @ILT+15690(??0ClusterExecutionException@cluster@@QEAA@AEBV01@@Z) 12 B 000010AA ??0ClusterExecutionException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z = @ILT+165(??0ClusterExecutionException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) 27 1A 000035D0 ??1ClusterException@cluster@@UEAA@XZ = @ILT+9675(??1ClusterException@cluster@@UEAA@XZ) 28 1B 00003C7E ??1ClusterExecutionException@cluster@@UEAA@XZ = @ILT+11385(??1ClusterExecutionException@cluster@@UEAA@XZ) 37 24 00002BD5 ??4ClusterException@cluster@@QEAAAEAV01@AEBV01@@Z = @ILT+7120(??4ClusterException@cluster@@QEAAAEAV01@AEBV01@@Z) 38 25 000034D1 ??4ClusterExecutionException@cluster@@QEAAAEAV01@AEBV01@@Z = @ILT+9420(??4ClusterExecutionException@cluster@@QEAAAEAV01@AEBV01@@Z) 46 2D 000D2220 ??_7ClusterException@cluster@@6B@ = ??_7ClusterException@cluster@@6B@ (const cluster::ClusterException::`vftable') 47 2E 000D2248 ??_7ClusterExecutionException@cluster@@6B@ = ??_7ClusterExecutionException@cluster@@6B@ (const cluster::ClusterExecutionException::`vftable') 52 33 00004BB5 ?clone@ClusterException@cluster@@UEAAPEAV12@XZ = @ILT+15280(?clone@ClusterException@cluster@@UEAAPEAV12@XZ) 53 34 00004D31 ?clone@ClusterExecutionException@cluster@@UEAAPEAV12@XZ = @ILT+15660(?clone@ClusterExecutionException@cluster@@UEAAPEAV12@XZ) 61 3C 00001D43 ?getErrorType@ClusterExecutionException@cluster@@QEBA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ = @ILT+3390(?getErrorType@ClusterExecutionException@cluster@@QEBA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ) 69 44 0000480E ?init@ClusterException@cluster@@IEAAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z = @ILT+14345(?init@ClusterException@cluster@@IEAAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) 78 4D 000032FB ?what@ClusterException@cluster@@UEBAPEBDXZ = @ILT+8950(?what@ClusterException@cluster@@UEBAPEBDXZ) Summary 4000 .data 5000 .idata 12000 .pdata 54000 .rdata 2000 .reloc 1000 .rsrc C9000 .text 1000 .tls
My python skills are rusty and I haven't tested this, so this may need further improvements, but try adding the exception translation method to handle the base class exception type:
static PyObject *clusterExecutionAsClusterExceptionType = NULL; static void translateClusterExecutionAsClusterException(ClusterException const &exception) { ClusterExecutionException* upcasted = dynamic_cast<ClusterExecutionException*>(&exception); if (upcasted) { assert(clusterExecutionAsClusterExceptionType != NULL); boost::python::object pythonExceptionInstance(*upcasted); PyErr_SetObject(clusterExecutionAsClusterExceptionType, pythonExceptionInstance.ptr()); } } register_exception_translator<ClusterException>(&translateClusterExecutionAsClusterException);
I don't know about your C++ code. But there is small problem with your python code. Catch ClusterExecutionException
before ClusterException
. You should always put child exception handler before base exception.
In test_exception
if ClusterExecutionException
raised it will be catch by ClusterException
before it could reach ClusterExecutionException
.
code should be as below code
def test_exception(exCase): try: cluster.boomTest(exCase) except ClusterExecutionException as ex: print 'Success! ClusterExecutionException gracefully handled:' \ '\n message="%s"' \ '\n errorType="%s"' \ '\n clusterResponse="%s"' % (ex.message, ex.errorType, ex.clusterResponse) except ClusterException as ex: print 'Success! ClusterException gracefully handled:' \ '\n message="%s"' % ex.message except: print 'Caught unknown exception: %s "%s"' % (sys.exc_info()[0], sys.exc_info()[1])
now do I have tried in Boost-Python while registering the exception translation of ClusterExecutionException to register it as its base ClusterException
, what you have mentioned in question.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With