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: Generator[object, None, None] | None = None, *args, **kwargs)
Bases:
RPCBatchProtocol
JSONRPC protocol implementation.
- JSON_RPC_VERSION = '2.0'
Currently, only version 2.0 is supported.
- request_factory() JSONRPCRequest
Factory for request objects.
Allows derived classes to use requests derived from
JSONRPCRequest
.- Return type:
- create_batch_request(requests: JSONRPCRequest | List[JSONRPCRequest] = None) JSONRPCBatchRequest
Create a new
JSONRPCBatchRequest
object.Called by the client when constructing a request.
- Parameters:
requests (
list
orJSONRPCRequest
) – A list of requests.- Returns:
A new request instance.
- Return type:
- create_request(method: str, args: List[Any] = None, kwargs: Dict[str, Any] = None, one_way: bool = False) JSONRPCRequest
Creates a new
JSONRPCRequest
object.Called by the client when constructing a request. JSON RPC allows either the
args
orkwargs
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:
- Raises:
InvalidRequestError – when
args
andkwargs
are both defined.
- parse_reply(data: bytes) JSONRPCSuccessResponse | JSONRPCErrorResponse | 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:
- Raises:
InvalidReplyError – if the response is not valid JSON or does not conform to the standard.
- parse_request(data: bytes) JSONRPCRequest | 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:
- Raises:
JSONRPCParseError – if the
data
cannot be parsed as valid JSON.JSONRPCInvalidRequestError – if the request does not comply with the standard.
- raise_error(error: JSONRPCErrorResponse | Dict[str, Any]) 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 adata
property.
- class tinyrpc.protocols.jsonrpc.JSONRPCRequest
Bases:
RPCRequest
Defines a JSON RPC request.
- one_way
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
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
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. TheRPCDispatcher
allows the use of public aliases in the@public
decorators. These are the names used in themethod
attribute.
- args
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 asmethod(*args)
.
- kwargs
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 asmethod(**kwargs)
.
- error_respond(error: Exception | str) 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) 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:
- 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:
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:
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(iterable=(), /)
Bases:
RPCBatchRequest
Defines a JSON RPC batch request.
- create_batch_response() JSONRPCBatchResponse | None
Produces a batch response object if a response is expected.
- Returns:
A batch response if needed
- Return type:
- 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(iterable=(), /)
Bases:
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 inkwargs
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() 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:
- class tinyrpc.protocols.jsonrpc.JSONRPCParseError(*args, **kwargs)
Bases:
FixedErrorMessageMixin
,InvalidRequestError
The request cannot be decoded or is malformed.
- class tinyrpc.protocols.jsonrpc.JSONRPCInvalidRequestError(*args, **kwargs)
Bases:
FixedErrorMessageMixin
,InvalidRequestError
The request contents are not valid for JSON RPC 2.0
- class tinyrpc.protocols.jsonrpc.JSONRPCMethodNotFoundError(*args, **kwargs)
Bases:
FixedErrorMessageMixin
,MethodNotFoundError
The requested method name is not found in the registry.
- class tinyrpc.protocols.jsonrpc.JSONRPCInvalidParamsError(*args, **kwargs)
Bases:
FixedErrorMessageMixin
,InvalidRequestError
The provided parameters are not appropriate for the function called.
- class tinyrpc.protocols.jsonrpc.JSONRPCInternalError(*args, **kwargs)
Bases:
FixedErrorMessageMixin
,InvalidRequestError
Unspecified error, not in the called function.
- class tinyrpc.protocols.jsonrpc.JSONRPCServerError(*args, **kwargs)
Bases:
FixedErrorMessageMixin
,InvalidRequestError
Unspecified error, this message originates from the called function.
- class tinyrpc.protocols.jsonrpc.JSONRPCError(error: JSONRPCErrorResponse | Dict[str, Any])
Bases:
FixedErrorMessageMixin
,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]}
}
}