RPC¶
Overview¶
Synchronous communication with lymph services is realised through RPC. RPC messages are sent via ØMQ. If a RPC call fails, it is the responsibility of the calling code to deal with it.
Registering methods as RPC callable¶
Any class inheriting from lymph.Interface
can receive RPC calls. By specifying the
name
argument when initializing the class, the lymph service will be reachable through its
interface name name
.
By default the service is registered under the name given when you configure the service.
import lymph
class EchoService(lymph.Interface):
pass
interfaces:
echo:
class: project.interfaces:EchoService
will be reachable with the service name echo
. This is the name with which lymph knows that
the RPC messages should be sent to EchoService
.
In order to make a method in a lymph interface class RPC callable, it is sufficient to
add the @lymph.rpc()
(or @lymph.raw_rpc()
for accessing the channel object) decorator in
front of it.
-
@
rpc
Marks the decorated interface method as an RPC method.
import lymph class Example(lymph.Interface): @lymph.raw_rpc() def do_ack(self, channel, message): """ HERE SOME FANCE HELP TEXT """ assert isinstance(channel, lymph.core.channels.ReplyChannel) assert isinstance(message, lymph.core.messages.Message) channel.ack() @lymph.rpc() def echo(self, message): return message
If a docstring is specified after the RPC method definition, it will be used as a description
of the service and will be returned by lymph inspect
.
Difference between lymph.rpc and lymph.raw_rpc¶
lymph.rpc
¶
The lymph.rpc()
decorator is easier to understand compared to lymph.raw_rpc()
since the former work as any Python function where what ever the RPC function return will be sent
to the caller, as for exceptions there is two cases depending on the raises
argument of
lymph.rpc()
:
- If the exception raised inside the RPC function is an instance of a class that is part of the
raises
argument then the client will see aRemoteError
. - Else the result will be a NACK.
lymph.raw_rpc
¶
When lymph.raw_rpc()
is used the underlying method call has to have the following form:
def some_rpc_method(self, channel, **kwargs):
…
The channel
argument takes a lymph.ReplyChannel
object which takes care of the communication
from and to the RPC caller. From within the responding method, you communicate through the channel
object with the calling party. The ReplyChannel
object provides you with the following methods:
-
reply
(body)¶ Parameters: body – reply sends
body
as a reply back to the callerimport lymph class EchoService(lymph.Interface): @lymph.raw_rpc() def echo(self, channel, text=None): channel.reply(text)
-
ack
(unless_reply_sent=False)¶ Parameters: unless_reply_sent – only send the acknowledgment if a reply has already been sent sends an acknowledgment to the caller.
-
nack
(unless_reply_sent=False)¶ Parameters: unless_reply_sent – only send the non-acknowledgment if a reply has already been sent sends a non-acknowledgment to the caller.
-
error
(body)¶ Parameters: body – error sends an error to the caller.
Sending RPC calls¶
In order to send RPC calls from within lymph services, you need to pass the call through
the proxy
class. You can obtain the system’s proxy by calling the proxy
method:
-
proxy
(address)¶ returns a proxy object that can be used to conveniently send requests to another service.
echo = self.proxy('echo') result = echo.upper(text='foo') assert result == 'FOO'
This is equivalent to
self.request('echo', 'echo.upper', text='foo')
.
The proxy object proxies any method that is called in the proxy class, into a corresponding RPC call. It does not however make sure, that the RPC call actually exists. It will send the call regardless of availability and timeout accordingly if no response is obtained.
Any value that is returned by the RPC call is also returned by the call to the corresponding
proxy method. In the example above, the service with the name echo
provides the upper(text)
endpoint. By calling the corresponding proxy method in the proxy object, the payload
text='foo'
is sent to the endpoint and its result returned and saved in the result
variable.
RPC calls are synchronous, i.e. program execution is halted until the RPC call returns an answer or it times out. If you require asynchronous communication, please refer to Events.
Deferred RPC calls¶
By default, RPC blocks until the response is received. A deferred RPC call mechanism is available if you wish to consume the RPC response later, or simply ingore it.
The call interface is similar to making a regular RPC call, with the addition of adding .defer call after it.
In that case, the call will return a Future (the actual implementation is a gevent AsyncResult which will block only when it’s .get method is called.
For instance:
echo = self.proxy('echo') result_future = echo.upper.defer(text='foo') # do other stuff result = result_future.get() assert result == 'FOO'