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 via myapi.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, the sender and receiver arguments of each method swap their roles. This is also possible upon initialization by giving invert=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.

unhandled_calls()[source]

Generator, returns the names of all incoming, unconnected methods.

If no results are returned, all incoming messages are connected. Use this to check for missed .connect calls.

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, call myapi.<method>.pass_secinfo(True). Listener calls then receive an additional kwarg called secinfo, containing the received dictionary. I.e. your handler(s) must add a secinfo= parameter in addition to the signature specified in the RemoteAPI.

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 the Codec 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 the RemoteAPI.

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 a Promise immediately.

If allow_positional_args=True, calls with positional (unnamed) arguments are accepted. Otherwise such arguments raise ValueError. 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).

quickrpc.codec(expression)[source]

Creates a codec from a given string expression.

The expression must be “<shorthand>:<specific parameters>”, with shorthand being the wanted Codec’s .shorthand property. For the specific parameters, see the respective Codec’s .fromstring method.

exception quickrpc.RemoteError(message, details)[source]

Bases: Exception

exception ‘quickrpc.RemoteError’ undocumented