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
orJSONRPCRequest
) – 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
orkwargs
argument to be set.Parameters: Returns: A new request instance
Return type: Raises: InvalidRequestError – when
args
andkwargs
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
orJSONRPCErrorResponse
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: Raises: - JSONRPCParseError – if the
data
cannot be parsed as valid JSON. - JSONRPCInvalidRequestError – if the request does not comply with the standard.
- JSONRPCParseError – if the
-
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 adata
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. TheRPCDispatcher
allows the use of public aliases in the@public
decorators. These are the names used in themethod
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 asmethod(*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 asmethod(**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
-
-
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.
-
-
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
-
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.
-
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
-
-
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.
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
Parameters: 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]}
}
}