diff --git a/capnp/capabilityHelper.h b/capnp/capabilityHelper.h index 8a314ab..e1e3294 100644 --- a/capnp/capabilityHelper.h +++ b/capnp/capabilityHelper.h @@ -11,6 +11,7 @@ extern "C" { ::kj::Promise * call_server_method(PyObject * py_server, char * name, capnp::CallContext< capnp::DynamicStruct, capnp::DynamicStruct> & context); PyObject * wrap_kj_exception(kj::Exception &); PyObject * wrap_kj_exception_for_reraise(kj::Exception &); + PyObject * get_exception_info(PyObject *, PyObject *, PyObject *); } void reraise_kj_exception() { @@ -36,8 +37,22 @@ void reraise_kj_exception() { void check_py_error() { PyObject * err = PyErr_Occurred(); if(err) { - // PyErr_Clear(); - throw std::exception(); + // TODO: decref references + PyObject * ptype, *pvalue, *ptraceback; + PyErr_Fetch(&ptype, &pvalue, &ptraceback); + + PyObject * info = get_exception_info(ptype, pvalue, ptraceback); + + PyObject * py_filename = PyTuple_GetItem(info, 0); + kj::String filename(kj::heapString(PyBytes_AsString(py_filename))); + + PyObject * py_line = PyTuple_GetItem(info, 1); + int line = PyInt_AsLong(py_line); + + PyObject * py_description = PyTuple_GetItem(info, 2); + kj::String description(kj::heapString(PyBytes_AsString(py_description))); + + throw kj::Exception(kj::Exception::Nature::OTHER, kj::Exception::Durability::PERMANENT, kj::mv(filename), line, kj::mv(description)); } } diff --git a/capnp/capnp.pyx b/capnp/capnp.pyx index ca2ca18..20f902c 100644 --- a/capnp/capnp.pyx +++ b/capnp/capnp.pyx @@ -16,6 +16,7 @@ from cython.operator cimport dereference as deref cimport async_cpp from cpython.ref cimport PyObject, Py_INCREF, Py_DECREF +from cpython.exc cimport PyErr_Clear from libc.stdint cimport * ctypedef unsigned int uint ctypedef uint8_t UInt8 @@ -181,6 +182,7 @@ class KjException(Exception): return self.message cdef public object wrap_kj_exception(capnp.Exception & exception): + PyErr_Clear() wrapper = _KjExceptionWrapper()._init(exception) ret = KjException(wrapper=wrapper) @@ -204,6 +206,13 @@ cdef public object wrap_kj_exception_for_reraise(capnp.Exception & exception): ret = KjException(wrapper=wrapper) return ret +cdef public object get_exception_info(object exc_type, object exc_obj, object exc_tb): + try: + return (exc_tb.tb_frame.f_code.co_filename, exc_tb.tb_lineno, repr(exc_type) + ':' + str(exc_obj)) + except: + return ('', 0, "Couldn't determine python exception") + + ctypedef fused _DynamicStructReaderOrBuilder: _DynamicStructReader _DynamicStructBuilder diff --git a/test/test_capability.py b/test/test_capability.py index 230abaa..100a994 100644 --- a/test/test_capability.py +++ b/test/test_capability.py @@ -113,7 +113,7 @@ def test_exception_client(capability): client = capability.TestInterface.new_client(BadServer(), loop) remote = client._send('foo', i=5) - with pytest.raises(ValueError): + with pytest.raises(capnp.KjException): loop.wait(remote) class BadPipelineServer: @@ -122,7 +122,7 @@ class BadPipelineServer: context.results.s = response.x + '_foo' context.results.outBox.cap = capability().TestInterface.new_server(Server(100)) def _error(error): - raise Exception('test') + raise Exception('test was a success') return context.params.inCap.foo(i=context.params.n).then(_then, _error) @@ -137,7 +137,7 @@ def test_exception_chain(capability): try: loop.wait(remote) except Exception as e: - assert str(e) == 'test' + assert 'test was a success' in str(e) def test_pipeline_exception(capability): loop = capnp.EventLoop()