diff --git a/capnp/capabilityHelper.h b/capnp/capabilityHelper.h index 4bb9e40..b81a50a 100644 --- a/capnp/capabilityHelper.h +++ b/capnp/capabilityHelper.h @@ -13,9 +13,27 @@ extern "C" { PyObject * wrapPyFunc(PyObject * func, PyObject * arg) { PyObject * result = PyObject_CallFunctionObjArgs(func, arg, NULL); Py_DECREF(func); + + PyObject * err = PyErr_Occurred(); + if(err) { + char * errorMsg = PyString_AsString(PyObject_Repr(err)); + // PyErr_Clear(); + throw std::invalid_argument(errorMsg); + } return result; } +void wrapRemoteCall(PyObject * func, capnp::Response & arg) { + wrap_remote_call(func, arg); + + PyObject * err = PyErr_Occurred(); + if(err) { + char * errorMsg = PyString_AsString(PyObject_Repr(err)); + // PyErr_Clear(); + throw std::invalid_argument(errorMsg); + } +} + ::kj::Promise evalLater(kj::EventLoop & loop, PyObject * func) { return loop.evalLater([func]() { return wrapPyFunc(func, NULL); } ); } @@ -38,9 +56,9 @@ PyObject * wrapPyFunc(PyObject * func, PyObject * arg) { ::kj::Promise then(::capnp::RemotePromise< ::capnp::DynamicStruct> & promise, PyObject * func, PyObject * error_func) { if(error_func == Py_None) - return promise.then([func](capnp::Response&& arg) { wrap_remote_call(func, arg); } ); + return promise.then([func](capnp::Response&& arg) { wrapRemoteCall(func, arg); } ); else - return promise.then([func](capnp::Response&& arg) { wrap_remote_call(func, arg); } + return promise.then([func](capnp::Response&& arg) { wrapRemoteCall(func, arg); } , [error_func](kj::Exception arg) { wrapPyFunc(error_func, wrap_kj_exception(arg)); } ); } @@ -60,7 +78,19 @@ public: kj::Promise call(capnp::InterfaceSchema::Method method, capnp::CallContext< capnp::DynamicStruct, capnp::DynamicStruct> context) { auto methodName = method.getProto().getName(); + kj::Promise * promise = call_server_method(py_server, const_cast(methodName.cStr()), context); + + PyObject * err = PyErr_Occurred(); + if(err) { + PyObject *ptype, *pvalue, *ptraceback; + PyErr_Fetch(&ptype, &pvalue, &ptraceback); + + char * errorMsg = PyString_AsString(pvalue); + PyErr_Clear(); + throw std::invalid_argument(errorMsg); + } + if(promise == nullptr) return kj::READY_NOW; diff --git a/capnp/capnp.pyx b/capnp/capnp.pyx index 48ae123..fbc51d2 100644 --- a/capnp/capnp.pyx +++ b/capnp/capnp.pyx @@ -39,30 +39,37 @@ import os as _os import sys as _sys import imp as _imp from functools import partial as _partial +import warnings as _warnings +import inspect as _inspect # By making it public, we'll be able to call it from capabilityHelper.h cdef public object wrap_dynamic_struct_reader(C_DynamicStruct.Reader & reader): return _DynamicStructReader()._init(reader, None) -cdef public void wrap_remote_call(PyObject * func, Response & r): +cdef public void wrap_remote_call(PyObject * func, Response & r) except *: response = _Response()._init_childptr(new Response(moveResponse(r)), None) func_obj = func # TODO: decref func? func_obj(response) -cdef public VoidPromise * call_server_method(PyObject * _server, char * _method_name, CallContext & _context): +cdef public VoidPromise * call_server_method(PyObject * _server, char * _method_name, CallContext & _context) except *: server = _server method_name = _method_name context = _CallContext()._init(_context) - ret = getattr(server, method_name)(context) + func = getattr(server, method_name) + ret = func(context) if ret is not None: if type(ret) is _VoidPromise: return new VoidPromise(moveVoidPromise(deref((<_VoidPromise>ret).thisptr))) else: - raise ValueError('Server function returned a value that was not a VoidPromise: ' + str(ret)) + try: + warning_msg = 'Server function (%s) returned a value that was not a VoidPromise: return = %s' % (method_name, str(ret)) + except: + warning_msg = 'Server function (%s) returned a value that was not a VoidPromise' % (method_name) + _warnings.warn_explicit(warning_msg, UserWarning, _inspect.getsourcefile(func), _inspect.getsourcelines(func)[1]) return NULL diff --git a/examples/example_capability.capnp b/examples/example_capability.capnp index 0bd862f..b947bda 100644 --- a/examples/example_capability.capnp +++ b/examples/example_capability.capnp @@ -29,17 +29,17 @@ interface TestInterface { # baz @2 (s: TestAllTypes); } -# interface TestExtends extends(TestInterface) { -# qux @0 (); +interface TestExtends extends(TestInterface) { + qux @0 (); # corge @1 TestAllTypes -> (); # grault @2 () -> TestAllTypes; -# } +} -# interface TestPipeline { -# getCap @0 (n: UInt32, inCap :TestInterface) -> (s: Text, outBox :Box); -# testPointers @1 (cap :TestInterface, obj :Object, list :List(TestInterface)) -> (); +interface TestPipeline { + getCap @0 (n: UInt32, inCap :TestInterface) -> (s: Text, outBox :Box); + testPointers @1 (cap :TestInterface, obj :Object, list :List(TestInterface)) -> (); -# struct Box { -# cap @0 :TestInterface; -# } -# } + struct Box { + cap @0 :TestInterface; + } +} diff --git a/test/test_capability.py b/test/test_capability.py index e08fb31..4352afc 100644 --- a/test/test_capability.py +++ b/test/test_capability.py @@ -98,3 +98,60 @@ def test_pipeline(capability): response = loop.wait_remote(remote) assert response.s == '26_foo' + +class BadServer: + def __init__(self, val=1): + self.val = val + + def foo(self, context): + context.results.x = str(context.params.i * 5 + self.val) + context.results.x2 = 5 # raises exception + +def test_exception_client(capability): + loop = capnp.EventLoop() + + client = capability.TestInterface.new_client(BadServer(), loop) + + remote = client._send('foo', i=5) + with pytest.raises(RuntimeError): + loop.wait_remote(remote) + +class BadPipelineServer: + def getCap(self, context): + def _then(response): + context.results.s = response.x + '_foo' + context.results.outBox.cap = capability().TestInterface.new_server(Server(100)) + def _error(error): + raise Exception('test') + + return context.params.inCap.foo(i=context.params.n).then(_then, _error) + +def test_exception_chain(capability): + loop = capnp.EventLoop() + + client = capability.TestPipeline.new_client(BadPipelineServer(), loop) + foo_client = capability.TestInterface.new_client(BadServer(), loop) + + remote = client.getCap(n=5, inCap=foo_client) + + try: + loop.wait_remote(remote) + except Exception as e: + assert e.message == 'test' + +def test_pipeline_exception(capability): + loop = capnp.EventLoop() + + client = capability.TestPipeline.new_client(BadPipelineServer(), loop) + foo_client = capability.TestInterface.new_client(BadServer(), loop) + + remote = client.getCap(n=5, inCap=foo_client) + + outCap = remote.outBox.cap + pipelinePromise = outCap.foo(i=10) + + with pytest.raises(Exception): + loop.wait_remote(pipelinePromise) + + with pytest.raises(Exception): + loop.wait_remote(remote)