jsonrpc-base

Pure python standard library JSONRPC data interface
git clone git://git.defalsify.org/python-jsonrpc-base.git
Log | Files | Refs | LICENSE

commit ae57339500442ef58e300360b7a479ac7e2aa88d
parent 02f99af868d96f5f76edd952bfeb588cee0c8fb2
Author: nolash <dev@holbrook.no>
Date:   Fri,  9 Apr 2021 12:33:21 +0200

Add parser

Diffstat:
M.gitignore | 3+++
Ajsonrpc_std/base.py | 4++++
Ajsonrpc_std/error.py | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajsonrpc_std/interface.py | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajsonrpc_std/parse.py | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 221 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,2 +1,5 @@ __pycache__ *.pyc +dist/ +*.egg-info +gmon.out diff --git a/jsonrpc_std/base.py b/jsonrpc_std/base.py @@ -0,0 +1,4 @@ +class JSONRPCBase: + major_version = 2 + minor_version = 0 + version_string = '2.0' diff --git a/jsonrpc_std/error.py b/jsonrpc_std/error.py @@ -0,0 +1,86 @@ +# local imports +from .base import JSONRPCBase + +class JSONRPCException(Exception, JSONRPCBase): + message = 'Unknown' + + def __init__(self, v): + context_v = '{} error'.format(self.message) + if v != None: + context_v += ': ' + v + + super(JSONRPCException, self).__init__(context_v) + + +class JSONRPCCustomException(JSONRPCException): + code = -32000 + message = 'Server' + + +class JSONRPCParseError(JSONRPCException): + code = -32700 + message = 'Parse' + + +class JSONRPCInvalidRequestError(JSONRPCException): + code = -32600 + message = 'Invalid request' + + +class JSONRPCMethodNotFoundError(JSONRPCException): + code = -32601 + message = 'Method not found' + + +class JSONRPCInvalidParametersError(JSONRPCException): + code = -32602 + message = 'Invalid parameters' + + +class JSONRPCInternalError(JSONRPCException): + code = -32603 + message = 'Internal' + + +class JSONRPCUnhandledErrorException(KeyError): + pass + + +class JSONRPCErrors: + reserved_max = -31999 + reserved_min = -32768 + local_max = -32000 + local_min = -32099 + + translations = { + -32700: JSONRPCParseError, + -32600: JSONRPCInvalidRequestError, + -32601: JSONRPCMethodNotFoundError, + -32602: JSONRPCInvalidParametersError, + -32603: JSONRPCInternalError, + } + + @classmethod + def add(self, code, exception_object): + if code < self.local_min or code > self.local_max: + raise ValueError('code must be in range <{},{}>'.format(self.local_min, self.local_max)) + exc = self.translations.get(code) + if exc != None: + raise ValueError('code already registered with {}'.format(exc)) + + if not issubclass(exception_object, JSONRPCCustomException): + raise ValueError('exception object must be a subclass of jsonrpc_base.error.JSONRPCCustomException') + + self.translations[code] = exception_object + + + @classmethod + def get(self, code, v=None): + e = self.translations.get(code) + if e == None: + raise JSONRPCUnhandledErrorException(code) + return e(v) + + +class InvalidJSONRPCError(ValueError): + pass diff --git a/jsonrpc_std/interface.py b/jsonrpc_std/interface.py @@ -0,0 +1,74 @@ +# standard imports +import uuid + +# local imports +from .base import JSONRPCBase +from .error import ( + JSONRPCErrors, + ) + + +class DefaultErrorParser: + + def translate(self, error): + code = error['error']['code'] + message = error['error']['message'] + if type(code).__name__ != 'int': + raise ValueError('error code is not int by {} in error {}'.format(type(code), error)) + + exc = None + try: + exc = JSONRPCErrors.get(code, message) + except KeyError: + return JSONRPCUndefinedError(code, message) + + +def jsonrpc_template(request_id=None): + if request_id == None: + request_id = str(uuid.uuid4()) + + return { + 'jsonrpc': JSONRPCBase.version_string, + 'id': request_id, + 'method': None, + 'params': [], + } + + +def jsonrpc_request(method, request_id=None): + req = jsonrpc_template(request_id=request_id) + req['method'] = method + return req + + +def jsonrpc_result(o, ep): + if o.get('error') != None: + raise ep.translate(o) + return o['result'] + + +def jsonrpc_response(request_id, result): + return { + 'jsonrpc': JSONRPCBase.version_string, + 'id': request_id, + 'result': result, + } + + +def jsonrpc_error(request_id, code, message=None): + e = JSONRPCErrors.get(code, message) + return { + 'jsonrpc': JSONRPCBase.version_string, + 'id': request_id, + 'error': { + 'code': code, + 'message': str(e), + }, + } + + +def jsonrpc_is_response_to(request, response): + return request['id'] == response['id'] + + + diff --git a/jsonrpc_std/parse.py b/jsonrpc_std/parse.py @@ -0,0 +1,54 @@ +# standard imports +import json + +# local imports +from .base import JSONRPCBase +from .error import ( + JSONRPCParseError, + JSONRPCInvalidRequestError, + ) +from .interface import ( + jsonrpc_request, + ) + + +def jsonrpc_validate_dict(o): + version = o.get('jsonrpc') + if version == None: + raise JSONRPCParseError('missing jsonrpc version field') + elif version != JSONRPCBase.version_string: + raise JSONRPCInvalidRequestError('Invalid version {}'.format(version)) + + method = o.get('method') + if method == None: + raise JSONRPCParseError('missing method field') + + params = o.get('params') + if params == None: + raise JSONRPCParseError('missing params field') + elif type(params).__name__ != 'list': + raise JSONRPCParseError('params field must be array') + + request_id = o.get('id') + if request_id == None: + raise JSONRPCParseError('missing id field') + if type(request_id).__name__ not in ['str', 'int']: + raise JSONRPCInvalidRequestError('invalid id value, must be string or integer') + + return o + + +def jsonrpc_from_str(s): + o = json.loads(s) + return jsonrpc_from_dict(o) + + +def jsonrpc_from_dict(o): + o = jsonrpc_validate_dict(o) + req = jsonrpc_request(o['method'], request_id=o['id']) + req['params'] = o['params'] + return req + +def jsonrpc_from_file(f): + o = json.load(f) + return jsonrpc_from_dict(o)