The protocol layer¶
Any protocol is implemented by deriving from
and implementing all of its members:
Base class for all protocol implementations.
create_request(method, args=None, kwargs=None, one_way=False)¶
Creates a new RPCRequest object.
It is up to the implementing protocol whether or not
kwargs, one of these, both at once or none of them are supported.
- method – The method name to invoke.
- args – The positional arguments to call the method with.
- kwargs – The keyword arguments to call the method with.
- one_way – The request is an update, i.e. it does not expect a reply.
Parses a reply and returns an
Returns: An instanced response.
Parses a request given as a string and returns an
Returns: An instanced request.
These require implementations of the following classes as well:
Creates an error response.
Create a response indicating that the request was parsed correctly, but an error has occured trying to fulfill it.
Parameters: error – An exception or a string describing the error. Returns: A response or
Noneto indicate that no error should be sent out.
Create a response.
Call this to return the result of a successful method invocation.
This creates and returns an instance of a protocol-specific subclass of
Parameters: result – Passed on to new response instance. Returns: A response or
Noneto indicate this request does not expect a response.
Returns a serialization of the request.
Returns: A string to be passed on to a transport.
RPC call response class.
Base class for all deriving responses.
Has an attribute
resultcontaining the result of the RPC call, unless an error occured, in which case an attribute
errorwill contain the error message.
Returns a serialization of the response.
Returns: A reply to be passed on to a transport.
Base class for all errors that caused the processing of a request to abort before a request object could be instantiated.
RPCErrorResponseto respond the error.
Returns: A error responce instance or
None, if the protocol decides to drop the error silently.
Every protocol deals with multiple kinds of structures:
data arguments are
always byte strings, either messages or replies, that are sent via or received
from a transport.
Finally, if an error occurs during parsing of a request, a
BadRequestError instance must be thrown. These need to be
subclassed for each protocol as well, since they generate error replies.
Some protocols may support batch requests. In this case, they need to derive
Handling a batch request is slightly different, while it supports
error_respond(), to make actual responses,
create_batch_response() needs to be used.
No assumptions are made whether or not it is okay for batch requests to be handled in parallel. This is up to the server/dispatch implementation, which must be chosen appropriately.
Multiple requests batched together.
A batch request is a subclass of
list. Protocols that support multiple requests in a single message use this to group them together.
Handling a batch requests is done in any order, responses must be gathered in a batch response and be in the same order as their respective requests.
Any item of a batch request is either a request or a subclass of
BadRequestError, which indicates that there has been an error in parsing the request.
Any supported protocol is used by instantiating its class and calling the
RPCProtocol. Note that constructors are not
part of the interface, any protocol may have specific arguments for its
Protocols usually live in their own module because they may need to import
optional modules that needn’t be a dependency for all of
The following example shows how to use the
JSONRPCProtocol class in a custom
application, without using any other components:
from tinyrpc.protocols.jsonrpc import JSONRPCProtocol from tinyrpc import BadRequestError, RPCBatchRequest rpc = JSONRPCProtocol() # the code below is valid for all protocols, not just JSONRPC: def handle_incoming_message(self, data): try: request = rpc.parse_request(data) except BadRequestError as e: # request was invalid, directly create response response = e.error_respond(e) else: # we got a valid request # the handle_request function is user-defined # and returns some form of response if hasattr(request, create_batch_response): response = request.create_batch_response( handle_request(req) for req in request ) else: response = handle_request(request) # now send the response to the client if response != None: send_to_client(response.serialize()) def handle_request(request): try: # do magic with method, args, kwargs... return request.respond(result) except Exception as e: # for example, a method wasn't found return request.error_respond(e)
from tinyrpc.protocols.jsonrpc import JSONRPCProtocol rpc = JSONRPCProtocol() # again, code below is protocol-independent # assuming you want to call method(*args, **kwargs) request = rpc.create_request(method, args, kwargs) reply = send_to_server_and_get_reply(request) response = rpc.parse_reply(reply) if hasattr(response, 'error'): # error handling... else: # the return value is found in response.result do_something_with(response.result)
Another example, this time using batch requests:
# or using batch requests: requests = rpc.create_batch_request([ rpc.create_request(method_1, args_1, kwargs_1) rpc.create_request(method_2, args_2, kwargs_2) # ... ]) reply = send_to_server_and_get_reply(request) responses = rpc.parse_reply(reply) for responses in response: if hasattr(reponse, 'error'): # ...
Finally, one-way requests are requests where the client does not expect an answer:
request = rpc.create_request(method, args, kwargs, one_way=True) send_to_server(request) # done