commit 6082d05093983f118f265e88917e210a64f96aee
Author: nolash <dev@holbrook.no>
Date: Fri, 9 Apr 2021 11:06:08 +0200
Initial commit
Diffstat:
9 files changed, 315 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,2 @@
+__pycache__
+*.pyc
diff --git a/jsonrpc_base/base.py b/jsonrpc_base/base.py
@@ -0,0 +1,4 @@
+class JSONRPCBase:
+ major_version = 2
+ minor_version = 0
+ version_string = '2.0'
diff --git a/jsonrpc_base/error.py b/jsonrpc_base/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_base/interface.py b/jsonrpc_base/interface.py
@@ -0,0 +1,68 @@
+# 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():
+ return {
+ 'jsonrpc': JSONRPCBase.version_string,
+ 'id': str(uuid.uuid4()),
+ 'method': None,
+ 'params': [],
+ }
+
+
+def jsonrpc_request(method):
+ req = jsonrpc_template()
+ 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/setup.cfg b/setup.cfg
@@ -0,0 +1,25 @@
+[metadata]
+name = jsonrpc_base
+version = 0.0.1a1
+description = Pure python standard library JSONRPC data interface
+author = Louis Holbrook
+author_email = dev@holbrook.no
+url = https://gitlab.com/nolash/jsonrpc_base
+keywords =
+ jsonrpc
+ rpc
+classifiers =
+ Programming Language :: Python :: 3
+ Operating System :: OS Independent
+ Development Status :: 3 - Alpha
+ Intended Audience :: Developers
+ License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
+ Topic :: Software Development :: Libraries
+license = GPL3
+licence_files =
+ LICENSE.txt
+
+[options]
+python_requires = >= 3.6
+packages =
+ jsonrpc_base
diff --git a/setup.py b/setup.py
@@ -0,0 +1,3 @@
+from setuptools import setup
+
+setup()
diff --git a/setup.pye b/setup.pye
@@ -0,0 +1,16 @@
+from setuptools import setup
+import configparser
+import os
+
+requirements = []
+f = open('requirements.txt', 'r')
+while True:
+ l = f.readline()
+ if l == '':
+ break
+ requirements.append(l.rstrip())
+f.close()
+
+setup(
+ install_requires=requirements,
+ )
diff --git a/tests/test_error.py b/tests/test_error.py
@@ -0,0 +1,72 @@
+# standard imports
+import unittest
+
+# local imports
+from jsonrpc_base.error import (
+ JSONRPCException,
+ JSONRPCCustomException,
+ JSONRPCErrors,
+ JSONRPCParseError,
+ )
+from jsonrpc_base.interface import (
+ jsonrpc_error,
+ DefaultErrorParser,
+ )
+
+class WrongError(Exception):
+ pass
+
+
+class RightError(JSONRPCCustomException):
+ message = 'Right'
+ code = -32001
+
+
+class TestError(unittest.TestCase):
+
+ def test_base(self):
+ e = JSONRPCException('foo')
+ self.assertEqual(str(e), 'Unknown error: foo')
+
+
+ def test_error_by_code(self):
+ e = JSONRPCErrors.get(-32700)
+ self.assertTrue(isinstance(e, JSONRPCParseError))
+
+
+ def test_custom_error(self):
+ with self.assertRaises(KeyError):
+ e = JSONRPCErrors.get(-1000)
+
+ with self.assertRaises(ValueError):
+ JSONRPCErrors.add(-32000, WrongError)
+
+ with self.assertRaises(ValueError):
+ JSONRPCErrors.add(JSONRPCErrors.local_min - 1, RightError)
+
+ with self.assertRaises(ValueError):
+ JSONRPCErrors.add(JSONRPCErrors.local_max + 1, RightError)
+
+ JSONRPCErrors.add(-32000, RightError)
+ e_retrieved = JSONRPCErrors.get(-32000, 'foo')
+ self.assertEqual(type(RightError(None)), type(e_retrieved))
+ self.assertEqual(str(e_retrieved), 'Right error: foo')
+
+
+ def test_error_interface(self):
+ uu = 'foo'
+ e = jsonrpc_error(uu, -32700)
+ self.assertEqual(e['error']['code'], -32700)
+ self.assertEqual(e['error']['message'], 'Parse error')
+
+
+ def test_default_error_translate(self):
+ uu = 'foo'
+ p = DefaultErrorParser()
+ e = jsonrpc_error(uu, -32700)
+ o = p.translate(e)
+ print('e {}'.format(o))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_request.py b/tests/test_request.py
@@ -0,0 +1,39 @@
+# standard imports
+import unittest
+
+# local imports
+from jsonrpc_base.base import JSONRPCBase
+from jsonrpc_base.interface import (
+ jsonrpc_request,
+ jsonrpc_response,
+ jsonrpc_result,
+ jsonrpc_is_response_to,
+ )
+
+
+class TestRequest(unittest.TestCase):
+
+ def test_request(self):
+ req = jsonrpc_request('foo_barBaz')
+ self.assertEqual(req['jsonrpc'], JSONRPCBase.version_string)
+ self.assertEqual(type(req['id']).__name__, 'str')
+ self.assertEqual(req['method'], 'foo_barBaz')
+ self.assertEqual(len(req['params']), 0)
+
+
+ def test_response(self):
+ res = jsonrpc_response('foo', 42)
+ self.assertEqual(res['id'], 'foo')
+ self.assertEqual(res['result'], 42)
+ r = jsonrpc_result(res, None)
+ self.assertEqual(r, 42)
+
+
+ def test_response_compare(self):
+ req = jsonrpc_request('foo_barBaz')
+ res = jsonrpc_response(req['id'], 42)
+ self.assertTrue(jsonrpc_is_response_to(req, res))
+
+
+if __name__ == '__main__':
+ unittest.main()