JSON-RPC transport implementation for Python supporting both 1.0 and 2.0 protocols with Django and Flask backends
—
Comprehensive error handling system with predefined JSON-RPC error types, custom exception support, and automatic error response generation. The library provides both JSON-RPC standard errors and Python exception integration.
Core error object representing JSON-RPC errors with code, message, and optional data fields.
class JSONRPCError:
serialize = staticmethod # json.dumps
deserialize = staticmethod # json.loads
def __init__(self, code: int = None, message: str = None, data = None):
"""
Create JSON-RPC error object.
Parameters:
- code: Error code (integer, -32768 to -32000 reserved)
- message: Brief error description (string)
- data: Additional error information (any type)
"""
@classmethod
def from_json(cls, json_str: str):
"""Create error from JSON string."""
# Properties
code: int
message: str
data # Any type
json: str # JSON representationStandard JSON-RPC error types with predefined codes and messages.
class JSONRPCParseError(JSONRPCError):
"""Invalid JSON was received by the server."""
CODE = -32700
MESSAGE = "Parse error"
class JSONRPCInvalidRequest(JSONRPCError):
"""The JSON sent is not a valid Request object."""
CODE = -32600
MESSAGE = "Invalid Request"
class JSONRPCMethodNotFound(JSONRPCError):
"""The method does not exist / is not available."""
CODE = -32601
MESSAGE = "Method not found"
class JSONRPCInvalidParams(JSONRPCError):
"""Invalid method parameter(s)."""
CODE = -32602
MESSAGE = "Invalid params"
class JSONRPCInternalError(JSONRPCError):
"""Internal JSON-RPC error."""
CODE = -32603
MESSAGE = "Internal error"
class JSONRPCServerError(JSONRPCError):
"""Reserved for implementation-defined server-errors."""
CODE = -32000
MESSAGE = "Server error"Standard Python exceptions for different error conditions.
class JSONRPCException(Exception):
"""Base JSON-RPC exception."""
pass
class JSONRPCInvalidRequestException(JSONRPCException):
"""Request is not valid."""
pass
class JSONRPCDispatchException(JSONRPCException):
"""
JSON-RPC dispatch exception for method implementations.
Should be thrown in dispatch methods to return custom errors.
"""
def __init__(self, code: int = None, message: str = None, data = None, *args, **kwargs):
"""
Create dispatch exception with error details.
Parameters:
- code: JSON-RPC error code
- message: Error message
- data: Additional error data
- args, kwargs: Standard exception arguments
"""
error: JSONRPCError # Associated error objectUtility functions for detecting and validating parameter errors.
def is_invalid_params(func, *args, **kwargs) -> bool:
"""
Check if function parameters are invalid.
Used internally to distinguish TypeError from invalid parameters
vs TypeError from within the function.
Parameters:
- func: Function to validate against
- args: Positional arguments
- kwargs: Keyword arguments
Returns:
True if parameters are invalid for the function
"""from jsonrpc import dispatcher, JSONRPCResponseManager
from jsonrpc.exceptions import JSONRPCDispatchException
@dispatcher.add_method
def divide(a, b):
if b == 0:
raise JSONRPCDispatchException(
code=-32602,
message="Division by zero",
data={"dividend": a, "divisor": b}
)
return a / b
@dispatcher.add_method
def validate_age(age):
if not isinstance(age, int) or age < 0:
raise JSONRPCDispatchException(
code=-32602,
message="Invalid age parameter",
data={"received": age, "expected": "positive integer"}
)
return {"valid": True, "age": age}
# Error response
request = '{"jsonrpc": "2.0", "method": "divide", "params": [10, 0], "id": 1}'
response = JSONRPCResponseManager.handle(request, dispatcher)
print(response.json)
# {
# "jsonrpc": "2.0",
# "error": {
# "code": -32602,
# "message": "Division by zero",
# "data": {"dividend": 10, "divisor": 0}
# },
# "id": 1
# }from jsonrpc.exceptions import (
JSONRPCMethodNotFound,
JSONRPCInvalidParams,
JSONRPCInternalError
)
# Method not found (automatically handled by manager)
request = '{"jsonrpc": "2.0", "method": "nonexistent", "id": 1}'
response = JSONRPCResponseManager.handle(request, dispatcher)
print(response.json)
# {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": 1}
# Invalid parameters (automatically detected)
@dispatcher.add_method
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
# Missing required parameter
request = '{"jsonrpc": "2.0", "method": "greet", "params": [], "id": 1}'
response = JSONRPCResponseManager.handle(request, dispatcher)
print(response.json)
# {"jsonrpc": "2.0", "error": {"code": -32602, "message": "Invalid params", "data": {...}}, "id": 1}
# Too many parameters
request = '{"jsonrpc": "2.0", "method": "greet", "params": ["Alice", "Hi", "Extra"], "id": 1}'
response = JSONRPCResponseManager.handle(request, dispatcher)
print(response.json)
# {"jsonrpc": "2.0", "error": {"code": -32602, "message": "Invalid params", "data": {...}}, "id": 1}from jsonrpc.exceptions import JSONRPCError
# Create custom error
custom_error = JSONRPCError(
code=-32001,
message="Database connection failed",
data={
"database": "users",
"host": "localhost:5432",
"timestamp": "2023-01-01T12:00:00Z"
}
)
print(custom_error.json)
# {
# "code": -32001,
# "message": "Database connection failed",
# "data": {
# "database": "users",
# "host": "localhost:5432",
# "timestamp": "2023-01-01T12:00:00Z"
# }
# }
# Use in dispatch exception
@dispatcher.add_method
def get_user(user_id):
try:
# Simulate database operation
if user_id == 999:
raise ConnectionError("Database unavailable")
return {"id": user_id, "name": f"User {user_id}"}
except ConnectionError as e:
raise JSONRPCDispatchException(
code=-32001,
message="Database connection failed",
data={"error": str(e), "user_id": user_id}
)from jsonrpc.exceptions import JSONRPCError
from jsonrpc.jsonrpc2 import JSONRPC20Response
# Manual error response creation
error = JSONRPCError(
code=-32603,
message="Internal error",
data={"component": "user_service", "error_id": "USR_001"}
)
response = JSONRPC20Response(error=error._data, _id=123)
print(response.json)
# {
# "jsonrpc": "2.0",
# "error": {
# "code": -32603,
# "message": "Internal error",
# "data": {"component": "user_service", "error_id": "USR_001"}
# },
# "id": 123
# }from jsonrpc import JSONRPCResponseManager
# Parse error (invalid JSON)
invalid_json = '{"jsonrpc": "2.0", "method": "test", invalid}'
response = JSONRPCResponseManager.handle(invalid_json, dispatcher)
print(response.json)
# {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}
# Invalid request structure
invalid_request = '{"jsonrpc": "2.0", "params": [1, 2], "id": 1}' # Missing method
response = JSONRPCResponseManager.handle(invalid_request, dispatcher)
print(response.json)
# {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}
# Batch with invalid request
batch_with_error = '''[
{"jsonrpc": "2.0", "method": "add", "params": [1, 2], "id": "1"},
{"jsonrpc": "2.0", "params": [3, 4], "id": "2"},
{"jsonrpc": "2.0", "method": "multiply", "params": [5, 6], "id": "3"}
]'''
response = JSONRPCResponseManager.handle(batch_with_error, dispatcher)
# Will return error for the invalid request in the batchfrom jsonrpc.exceptions import JSONRPCDispatchException
import logging
logger = logging.getLogger(__name__)
@dispatcher.add_method
def process_file(filename):
try:
# Simulate file processing
if not filename:
raise ValueError("Filename cannot be empty")
if not filename.endswith('.txt'):
raise JSONRPCDispatchException(
code=-32602,
message="Invalid file type",
data={"filename": filename, "supported": [".txt"]}
)
# Simulate processing
return {"status": "processed", "filename": filename}
except ValueError as e:
logger.error(f"Validation error: {e}")
raise JSONRPCDispatchException(
code=-32602,
message="Validation failed",
data={"error": str(e)}
)
except Exception as e:
logger.exception("Unexpected error processing file")
raise JSONRPCDispatchException(
code=-32603,
message="Internal processing error",
data={"filename": filename, "error_type": type(e).__name__}
)
# Test error cases
request1 = '{"jsonrpc": "2.0", "method": "process_file", "params": [""], "id": 1}'
response1 = JSONRPCResponseManager.handle(request1, dispatcher)
# Returns validation error
request2 = '{"jsonrpc": "2.0", "method": "process_file", "params": ["doc.pdf"], "id": 2}'
response2 = JSONRPCResponseManager.handle(request2, dispatcher)
# Returns invalid file type error# Standard JSON-RPC error codes
PARSE_ERROR = -32700 # Invalid JSON
INVALID_REQUEST = -32600 # Invalid request object
METHOD_NOT_FOUND = -32601 # Method doesn't exist
INVALID_PARAMS = -32602 # Invalid parameters
INTERNAL_ERROR = -32603 # Internal JSON-RPC error
# Server error range: -32000 to -32099 (reserved for implementation)
DATABASE_ERROR = -32001
AUTHENTICATION_ERROR = -32002
AUTHORIZATION_ERROR = -32003
RATE_LIMIT_ERROR = -32004
VALIDATION_ERROR = -32005
# Application-specific errors: -32100 and below
USER_NOT_FOUND = -32100
INSUFFICIENT_FUNDS = -32101
PRODUCT_OUT_OF_STOCK = -32102
@dispatcher.add_method
def transfer_funds(from_account, to_account, amount):
if amount <= 0:
raise JSONRPCDispatchException(
code=VALIDATION_ERROR,
message="Invalid transfer amount",
data={"amount": amount}
)
# Check balance
if get_balance(from_account) < amount:
raise JSONRPCDispatchException(
code=INSUFFICIENT_FUNDS,
message="Insufficient funds for transfer",
data={
"from_account": from_account,
"requested": amount,
"available": get_balance(from_account)
}
)
# Perform transfer
return {"status": "success", "transaction_id": "TXN123"}Install with Tessl CLI
npx tessl i tessl/pypi-json-rpc