quickrpc package¶
QuickRPC is a library for quick and painless setup of communication channels and Remote-call protocols.
Python 3 only
To use QuickRPC, you define a RemoteAPI
subclass. This is a special
interface-like class whose methods define the possible incoming and outgoing
calls.
Second, a Codec
is needed to translate method calls into byte strings
and vice-versa. This could for example be JSON-RPC or MsgPack codec.
Third, the RemoteAPI is bound to a Transport
. This is
basically a send-and-receive channel out of your program. Predefined transports
include Stdio, TCP client and server as well as UDP. Additionally there are
wrappers that can merge multiple transports together and restart a failing
transport.
Codecs and Transports can be instantiated from a textual definition, so that
they can easily put in a config file or on the commandline. See
transport()
(alias of transports.Transport.fromstring()
) and
codec()
(alias of codecs.Codec.fromstring()
).
-
class
quickrpc.
RemoteAPI
(codec='jrpc', transport=None, security='null', invert=False, async_processing=False)[source]¶ Bases:
object
Describes an API i.e. a set of allowed outgoing and incoming calls.
Subclass and add your calls.
codec
holds the Codec for (de)serializing data.transport
holds the underlying transport.security
holds the security provider.Both can also be strings, then
Transport.fromstring()
/Codec.fromstring()
are used to acquire the respective objects. In this case, transport still needs to be started viamyapi.transport.start()
.Methods marked as
@outgoing
are automatically turned into messages when called. The method body is executed before sending. (use e.g. for validation of outgoing data). They must accept a special receivers argument, which is passed to the Transport.Methods marked as
@incoming
are called by the transport when messages arrive. They work like signals - you can connect your own handler(s) to them. Connected handlers must have the same signature as the incoming call. All @incoming methods MUST support a senders argument.Connect like this:
>>> def handler(self, foo=None): pass >>> remote_api.some_method.connect(handler) >>> # later >>> remote_api.some_method.disconnect(handler)
Execution order: the method of remote_api is executed first, then the connected handlers in the order of registering.
Incoming messages with unknown method will not be processed. If the message has
.id != 0
, it will automatically be replied with an error.Threading:
- outgoing messages are sent on the calling thread.
- If async_processing = False, incoming messages are handled on the thread which handles Transport receive events. I.e. the Transport implementation defines the behaviour.
- If async_processing = True, an extra Thread is used to handle messages.
The latter allows the receive handler to run concurrently to message handling, allowing further requests to be sent out and to await the result. However it means one extra thread. In any case, only one incoming message is handled at a time.
Recommendation is to set
async_processing=True
if there are any outgoing calls that have a reply,False
if not.Inverting:
You can
invert()
the whole api, swapping incoming and outgoing methods. When inverted, thesender
andreceiver
arguments of each method swap their roles. This is also possible upon initialization by givinginvert=True
kwarg.-
invert
()[source]¶ Swaps
@incoming
and@outgoing
decoration on all methods of this INSTANCE.I.e. generates the opposite-side API.
Do this before connecting any handlers to incoming calls.
You can achieve the same effect by instantiating with
invert=True
kwarg.
-
message_error
(sender, exception, in_reply_to=None)[source]¶ Called each time that an incoming message causes problems.
By default, it logs the error as warning. in_reply_to is the message that triggered the error, None if decoding failed. If the requested method can be identified and has a reply, an error reply is returned to the sender.
-
transport
¶ Gets/sets the transport used to send and receive messages.
You can change the transport at runtime.
-
quickrpc.
incoming
(unbound_method=None, has_reply=False, allow_positional_args=False)[source]¶ Marks a method as possible incoming message.
@incoming(has_reply=False, allow_positional_args=False)
Incoming methods keep list of connected listeners, which are called with the signature of the incoming method (excluding
self
). The first argument will be passed positional and is a string describing the sender of the message. The remaining arguments can be chosen freely and will usually be passed as named args.Optionally, you can receive security info (the
secinfo
dict extracted from the message). For this, callmyapi.<method>.pass_secinfo(True)
. Listener calls then receive an additional kwarg calledsecinfo
, containing the received dictionary. I.e. your handler(s) must add asecinfo=
parameter in addition to the signature specified in theRemoteAPI
.Listeners can be added with
myapi.<method>.connect(handler)
and disconnected with.disconnect(handler)
. They are called in the order that they were added.If
has_reply=True
, the handler should return a value that is sent back to the sender. If multiple handlers are connected, at most one of them must return something.- Notice:
Processing of incoming messages does not resume until all listeners returned. This means that if you issue a followup remote call in a listener, the result can not arrive while the listener is executing. If you want to do this, use promise.then() to resume when the result is there.
You can also spawn a new thread in your listener, to do the processing. However, be aware that this makes you vulnerable against DOS attacks, since an attacker can make you open arbitrary many threads this way.
If
allow_positional_args=True
, messages with positional (unnamed) arguments are accepted. Otherwise such arguments throw an error message without executing the handler(s). Note that theCodec
must support positional and/or mixed args as well. It is strongly recommended to use named args only.Lastly, the incoming method has a
myapi.<method>.inverted()
method, which will return the@outgoing
variant of it.
-
quickrpc.
outgoing
(unbound_method=None, has_reply=False, allow_positional_args=False)[source]¶ Marks a method as possible outgoing message.
@outgoing(has_reply=False, allow_position_args=False)
Invocation of outgoing methods leads to a message being sent over the
Transport
of theRemoteAPI
.The first argument must be the list of receivers of the message, as a list of strings. When calling the method, usually you will use the sender name(s) received via an incoming call. Set receivers=None to send to all connected peers.
The remaining arguments can be choosen freely. The argument values can be anything supported by the
Codec
that you use. The builtin Codecs support all the “atomic” builtin types, as well as dicts and lists.If
has_reply=True
, the other side is expected to return a result value. In this case, calling the outgoing method returns aPromise
immediately.If
allow_positional_args=True
, calls with positional (unnamed) arguments are accepted. Otherwise such arguments raiseValueError
. For sending, they will be converted into named arguments. It is strongly recommended to use named args only.Lastly, the outgoing method has a
myapi.<method>.inverted()
method, which will return the@incoming
variant of it.
-
quickrpc.
transport
(expression)[source]¶ Creates a transport from a given string expression.
The expression must be “<shorthand>:<specific parameters>”, with shorthand being the wanted transport’s .shorthand property. For the specific parameters, see the respective transport’s .fromstring method.
The base class implementation searches among all known subclasses for the Transport matching the given shorthand, and returns
Subclass.fromstring(expression)
.