The JSON-RPC protocol

Example

The following example shows how to use the JSONRPCProtocol class in a custom application, without using any other components:

Server

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)

Client

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

Protocol implementation

API Reference

class tinyrpc.protocols.jsonrpc.JSONRPCProtocol(id_generator: Optional[Generator[object, None, None], None] = None, *args, **kwargs)

Bases: tinyrpc.protocols.RPCBatchProtocol

JSONRPC protocol implementation.

JSON_RPC_VERSION = '2.0'

Currently, only version 2.0 is supported.

request_factory() → tinyrpc.protocols.jsonrpc.JSONRPCRequest

Factory for request objects.

Allows derived classes to use requests derived from JSONRPCRequest.

Return type:JSONRPCRequest
create_batch_request(requests: Union[JSONRPCRequest, List[JSONRPCRequest]] = None) → tinyrpc.protocols.jsonrpc.JSONRPCBatchRequest

Create a new JSONRPCBatchRequest object.

Called by the client when constructing a request.

Parameters:requests (list or JSONRPCRequest) – A list of requests.
Returns:A new request instance.
Return type:JSONRPCBatchRequest
create_request(method: str, args: List[Any] = None, kwargs: Dict[str, Any] = None, one_way: bool = False) → tinyrpc.protocols.jsonrpc.JSONRPCRequest

Creates a new JSONRPCRequest object.

Called by the client when constructing a request. JSON RPC allows either the args or kwargs argument to be set.

Parameters:
  • method (str) – The method name to invoke.
  • args (list) – The positional arguments to call the method with.
  • kwargs (dict) – The keyword arguments to call the method with.
  • one_way (bool) – The request is an update, i.e. it does not expect a reply.
Returns:

A new request instance

Return type:

JSONRPCRequest

Raises:

InvalidRequestError – when args and kwargs are both defined.

parse_reply(data: bytes) → Union[tinyrpc.protocols.jsonrpc.JSONRPCSuccessResponse, tinyrpc.protocols.jsonrpc.JSONRPCErrorResponse, tinyrpc.protocols.jsonrpc.JSONRPCBatchResponse]

De-serializes and validates a response.

Called by the client to reconstruct the serialized JSONRPCResponse.

Parameters:data (bytes) – The data stream received by the transport layer containing the serialized request.
Returns:A reconstructed response.
Return type:JSONRPCSuccessResponse or JSONRPCErrorResponse
Raises:InvalidReplyError – if the response is not valid JSON or does not conform to the standard.
parse_request(data: bytes) → Union[tinyrpc.protocols.jsonrpc.JSONRPCRequest, tinyrpc.protocols.jsonrpc.JSONRPCBatchRequest]

De-serializes and validates a request.

Called by the server to reconstruct the serialized JSONRPCRequest.

Parameters:

data (bytes) – The data stream received by the transport layer containing the serialized request.

Returns:

A reconstructed request.

Return type:

JSONRPCRequest

Raises:
raise_error(error: Union[JSONRPCErrorResponse, Dict[str, Any]]) → tinyrpc.protocols.jsonrpc.JSONRPCError

Recreates the exception.

Creates a JSONRPCError instance and raises it. This allows the error, message and data attributes of the original exception to propagate into the client code.

The raises_error flag controls if the exception object is raised or returned.

Returns:the exception object if it is not allowed to raise it.
Raises:JSONRPCError – when the exception can be raised. The exception object will contain message, code and optionally a data property.
class tinyrpc.protocols.jsonrpc.JSONRPCRequest

Bases: tinyrpc.protocols.RPCRequest

Defines a JSON RPC request.

one_way = None

Request or Notification.

Type:bool

This flag indicates if the client expects to receive a reply (request: one_way = False) or not (notification: one_way = True).

Note that according to the specification it is possible for the server to return an error response. For example if the request becomes unreadable and the server is not able to determine that it is in fact a notification an error should be returned. However, once the server had verified that the request is a notification no reply (not even an error) should be returned.

unique_id = None

Correlation ID used to match request and response.

Type:int or str

Generated by the client, the server copies it from request to corresponding response.

method = None

The name of the RPC function to be called.

Type:str

The method attribute uses the name of the function as it is known by the public. The RPCDispatcher allows the use of public aliases in the @public decorators. These are the names used in the method attribute.

args = None

The positional arguments of the method call.

Type:list

The contents of this list are the positional parameters for the method called. It is eventually called as method(*args).

kwargs = None

The keyword arguments of the method call.

Type:dict

The contents of this dict are the keyword parameters for the method called. It is eventually called as method(**kwargs).

error_respond(error: Union[Exception, str]) → Optional[tinyrpc.protocols.jsonrpc.JSONRPCErrorResponse, None]

Create an error response to this request.

When processing the request produces an error condition this method can be used to create the error response object.

Parameters:error (Exception or str) – Specifies what error occurred.
Returns:An error response object that can be serialized and sent to the client.
Return type:;py:class:JSONRPCErrorResponse
respond(result: Any) → Optional[tinyrpc.protocols.jsonrpc.JSONRPCSuccessResponse, None]

Create a response to this request.

When processing the request completed successfully this method can be used to create a response object.

Parameters:result (Anything that can be encoded by JSON.) – The result of the invoked method.
Returns:A response object that can be serialized and sent to the client.
Return type:JSONRPCSuccessResponse
serialize() → bytes

Returns a serialization of the request.

Converts the request into a bytes object that can be sent to the server.

Returns:The serialized encoded request object.
Return type:bytes
class tinyrpc.protocols.jsonrpc.JSONRPCSuccessResponse

Bases: tinyrpc.protocols.RPCResponse

Collects the attributes of a successful response message.

Contains the fields of a normal (i.e. a non-error) response message.

unique_id

Correlation ID to match request and response. A JSON RPC response must have a defined matching id attribute. None is not a valid value for a successful response.

Type:str or int
result

Contains the result of the RPC call.

Type:Any type that can be serialized by the protocol.
serialize() → bytes

Returns a serialization of the response.

Converts the response into a bytes object that can be passed to and by the transport layer.

Returns:The serialized encoded response object.
Return type:bytes
class tinyrpc.protocols.jsonrpc.JSONRPCErrorResponse

Bases: tinyrpc.protocols.RPCErrorResponse

Collects the attributes of an error response message.

Contains the fields of an error response message.

unique_id

Correlation ID to match request and response. None is a valid ID when the error cannot be matched to a particular request.

Type:str or int or None
error

The error message. A string describing the error condition.

Type:str
data

This field may contain any JSON encodable datum that the server may want to return the client.

It may contain additional information about the error condition, a partial result or whatever. Its presence and value are entirely optional.

Type:Any type that can be serialized by the protocol.
_jsonrpc_error_code

The numeric error code.

The value is usually predefined by one of the JSON protocol exceptions. It can be set by the developer when defining application specific exceptions. See FixedErrorMessageMixin for an example on how to do this.

Note that the value of this field must comply with the defined values in the standard.

serialize() → bytes

Returns a serialization of the error.

Converts the response into a bytes object that can be passed to and by the transport layer.

Returns:The serialized encoded error object.
Return type:bytes

Batch protocol

API Reference

class tinyrpc.protocols.jsonrpc.JSONRPCBatchRequest

Bases: tinyrpc.protocols.RPCBatchRequest

Defines a JSON RPC batch request.

create_batch_response() → Optional[tinyrpc.protocols.jsonrpc.JSONRPCBatchResponse, None]

Produces a batch response object if a response is expected.

Returns:A batch response if needed
Return type:JSONRPCBatchResponse
serialize() → bytes

Returns a serialization of the request.

Converts the request into a bytes object that can be passed to and by the transport layer.

Returns:A bytes object to be passed on to a transport.
Return type:bytes
class tinyrpc.protocols.jsonrpc.JSONRPCBatchResponse

Bases: tinyrpc.protocols.RPCBatchResponse

Multiple responses from a batch request. See JSONRPCBatchRequest on how to handle.

Items in a batch response need to be JSONRPCResponse instances or None, meaning no reply should be generated for the request.

serialize() → bytes

Returns a serialization of the batch response.

Converts the response into a bytes object that can be passed to and by the transport layer.

Returns:A bytes object to be passed on to a transport.
Return type:bytes

Errors and error handling

API Reference

class tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin(*args, **kwargs)

Bases: object

Combines JSON RPC exceptions with the generic RPC exceptions.

Constructs the exception using the provided parameters as well as properties of the JSON RPC Exception.

JSON RPC exceptions declare two attributes:

jsonrpc_error_code

This is an error code conforming to the JSON RPC error codes convention.

Type:int
message

This is a textual representation of the error code.

Type:str
Parameters:
  • args (list) – Positional arguments for the constructor. When present it overrules the message attribute.
  • kwargs (dict) – Keyword arguments for the constructor. If the data parameter is found in kwargs its contents are used as the data property of the JSON RPC Error object.

FixedErrorMessageMixin is the basis for adding your own exceptions to the predefined ones. Here is a version of the reverse string example that dislikes palindromes:

class PalindromeError(FixedErrorMessageMixin, Exception)
    jsonrpc_error_code = 99
    message = "Ah, that's cheating"

@public
def reverse_string(s):
    r = s[::-1]
    if r == s:
        raise PalindromeError(data=s)
    return r
>>> client.reverse('rotator')

Will return an error object to the client looking like:

{
    "jsonrpc": "2.0",
    "id": 1,
    "error": {
        "code": 99,
        "message": "Ah, that's cheating",
        "data": "rotator"
    }
}
error_respond() → tinyrpc.protocols.jsonrpc.JSONRPCErrorResponse

Converts the error to an error response object.

Returns:An error response object ready to be serialized and sent to the client.
Return type:JSONRPCErrorResponse
class tinyrpc.protocols.jsonrpc.JSONRPCParseError(*args, **kwargs)

Bases: tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin, tinyrpc.exc.InvalidRequestError

The request cannot be decoded or is malformed.

class tinyrpc.protocols.jsonrpc.JSONRPCInvalidRequestError(*args, **kwargs)

Bases: tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin, tinyrpc.exc.InvalidRequestError

The request contents are not valid for JSON RPC 2.0

class tinyrpc.protocols.jsonrpc.JSONRPCMethodNotFoundError(*args, **kwargs)

Bases: tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin, tinyrpc.exc.MethodNotFoundError

The requested method name is not found in the registry.

class tinyrpc.protocols.jsonrpc.JSONRPCInvalidParamsError(*args, **kwargs)

Bases: tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin, tinyrpc.exc.InvalidRequestError

The provided parameters are not appropriate for the function called.

class tinyrpc.protocols.jsonrpc.JSONRPCInternalError(*args, **kwargs)

Bases: tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin, tinyrpc.exc.InvalidRequestError

Unspecified error, not in the called function.

class tinyrpc.protocols.jsonrpc.JSONRPCServerError(*args, **kwargs)

Bases: tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin, tinyrpc.exc.InvalidRequestError

Unspecified error, this message originates from the called function.

class tinyrpc.protocols.jsonrpc.JSONRPCError(error: Union[JSONRPCErrorResponse, Dict[str, Any]])

Bases: tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin, tinyrpc.exc.RPCError

Reconstructs (to some extend) the server-side exception.

The client creates this exception by providing it with the error attribute of the JSON error response object returned by the server.

Parameters:error (dict) –

This dict contains the error specification:

  • code (int): the numeric error code.
  • message (str): the error description.
  • data (any): if present, the data attribute of the error

Adding custom exceptions

Note

As per the specification you should use error codes -32000 to -32099 when adding server specific error messages. Error codes outside the range -32768 to -32000 are available for application specific error codes.

To add custom errors you need to combine an Exception subclass with the FixedErrorMessageMixin class to create your exception object which you can raise.

So a version of the reverse string example that dislikes palindromes could look like:

from tinyrpc.protocols.jsonrpc import FixedErrorMessageMixin, JSONRPCProtocol
from tinyrpc.dispatch import RPCDispatcher

dispatcher = RPCDispatcher()

class PalindromeError(FixedErrorMessageMixin, Exception):
    jsonrpc_error_code = 99
    message = "Ah, that's cheating!"


@dispatcher.public
def reverse_string(s):
    r = s[::-1]
    if r == s:
        raise PalindromeError()
    return r

Error with data

The specification states that the error element of a reply may contain an optional data property. This property is now available for your use.

There are two ways that you can use to pass additional data with an Exception. It depends whether your application generates regular exceptions or exceptions derived from FixedErrorMessageMixin.

When using ordinary exceptions you normally pass a single parameter (an error message) to the Exception constructor. By passing two parameters, the second parameter is assumed to be the data element.

@public
def fn():
    raise Exception('error message', {'msg': 'structured data', 'lst': [1, 2, 3]})

This will produce the reply message:

{   "jsonrpc": "2.0",
    "id": <some id>,
    "error": {
        "code": -32000,
        "message": "error message",
        "data": {"msg": "structured data", "lst": [1, 2, 3]}
    }
}

When using FixedErrorMessageMixin based exceptions the data is passed using a keyword parameter.

class MyException(FixedErrorMessageMixin, Exception):
    jsonrcp_error_code = 99
    message = 'standard message'

@public
def fn():
    raise MyException(data={'msg': 'structured data', 'lst': [1, 2, 3]})

This will produce the reply message:

{   "jsonrpc": "2.0",
    "id": <some id>,
    "error": {
        "code": 99,
        "message": "standard message",
        "data": {"msg": "structured data", "lst": [1, 2, 3]}
    }
}