This commit is contained in:
2025-06-21 02:26:54 +03:00
parent 8c9cd425c5
commit e1fbc591f8
6 changed files with 93 additions and 43 deletions

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "jsonrpc" name = "jsonrpc"
version = "1.0" version = "2.0"
authors = [ authors = [
{ name="RemiZOffAlex", email="remizoffalex@gmail.com" }, { name="RemiZOffAlex", email="remizoffalex@gmail.com" },
] ]

View File

@@ -1,7 +1,6 @@
__author__ = 'RemiZOffAlex' __author__ = 'RemiZOffAlex'
__email__ = 'remizoffalex@mail.ru' __email__ = 'remizoffalex@mail.ru'
import json
import logging import logging
import traceback import traceback
@@ -27,7 +26,7 @@ class Response:
class Method: class Method:
def __init__(self, function, middlewares = [], debug: bool = False): def __init__(self, function, middlewares=[], debug: bool = False):
self.function = function self.function = function
self.middlewares = middlewares self.middlewares = middlewares
self.debug = debug self.debug = debug
@@ -65,11 +64,9 @@ class Method:
response = middleware(query) response = middleware(query)
return response return response
response = self.exec_function(query) response = self.exec_function(query)
return response return response
def __repr__(self): def __repr__(self):
return '<{}>'.format(self.function) return '<{}>'.format(self.function)
@@ -86,15 +83,15 @@ class JSONRPC:
self.methods = {} self.methods = {}
self.debug = debug self.debug = debug
def method(self, name: str, middlewares = None): def method(self, name: str, middlewares=None):
"""Декоратор метода """Декоратор метода
""" """
assert len(name) > 0, 'Не указано имя метода' assert len(name) > 0, 'Не указано имя метода'
def wrap(function): def wrap(function):
method = Method( method = Method(
function = function, function=function,
middlewares = middlewares middlewares=middlewares
) )
self.methods[name] = method self.methods[name] = method
return function return function
@@ -151,31 +148,31 @@ class JSONRPC:
def validate(self, query): def validate(self, query):
"""Валидация запроса """Валидация запроса
""" """
keys = query.keys()
if 'id' not in query:
return InvalidRequestError(
id=query['id'],
message=f'Некорректный запрос: {query}'
).__json__()
if 'method' not in query: if 'method' not in query:
return InvalidRequestError( result = InvalidRequestError(
id=query['id'],
message=f'Некорректный запрос: {query}' message=f'Некорректный запрос: {query}'
).__json__() )
if 'id' in query:
result.id = query['id']
return result
def process(self, query): def process(self, query):
"""Выполнение метода """Выполнение метода
""" """
self.validate(query) result = self.validate(query)
if isinstance(result, JSONRPCError):
return result
name = query['method'] name = query['method']
if name not in self.methods: if name not in self.methods:
if 'id' in query:
__id = query['id']
else:
__id = None
result = MethodNotFoundError( result = MethodNotFoundError(
query['id'], message=f'Метод не найден: {name}',
message=f'Метод не найден: {name}' id=__id
) )
return result.__json__() return result
method = self.methods[name] method = self.methods[name]
@@ -198,11 +195,14 @@ class JSONRPC:
# message=traceback.format_exc() # message=traceback.format_exc()
) )
else: else:
response = Response( if 'id' in query:
id=query['id'], response = Response(
result=response id=query['id'],
) result=response
result = response.__json__() )
else:
return
result = response
return result return result
def __call__(self, queries): def __call__(self, queries):
@@ -213,7 +213,9 @@ class JSONRPC:
elif isinstance(queries, list): elif isinstance(queries, list):
result = [] result = []
for query in queries: for query in queries:
result.append(self.process(query)) response = self.process(query)
if response:
result.append(response)
return result return result
def __getitem__(self, key): def __getitem__(self, key):

View File

@@ -2,19 +2,30 @@ __author__ = 'RemiZOffAlex'
__email__ = 'remizoffalex@mail.ru' __email__ = 'remizoffalex@mail.ru'
import jinja2 import jinja2
import logging
import pathlib import pathlib
import aiohttp_jinja2 import aiohttp_jinja2
from aiohttp.web import Response, json_response from aiohttp.web import Response, json_response
from .. import JSONRPC from .. import JSONRPC
from ..exceptions import ParseError
log = logging.getLogger(__name__)
pathlib.Path(__file__).parent.resolve() pathlib.Path(__file__).parent.resolve()
class APIHandler: class APIHandler:
def __init__(self, jsonrpc): def __init__(
self,
jsonrpc,
debug: bool = False
):
self.jsonrpc = jsonrpc self.jsonrpc = jsonrpc
self.debug = debug
if self.debug:
log.debug('Connect JSON-RPC to aiohttp complete')
@aiohttp_jinja2.template('api_browse.html') @aiohttp_jinja2.template('api_browse.html')
async def get(self, request) -> Response: async def get(self, request) -> Response:
@@ -25,9 +36,16 @@ class APIHandler:
return pagedata return pagedata
async def post(self, request) -> Response: async def post(self, request) -> Response:
json_data = await request.json() try:
result = self.jsonrpc(json_data) json_data = await request.json()
return json_response(result) result = self.jsonrpc(json_data)
return json_response(result)
except ValueError as e:
log.error('invalid json: %s', request_data)
log.exception(e)
raise ParseError(
message={'message': 'Invalid JSON: {0!r}'.format(request_data)}
)
def api_init(app, jsonrpc: JSONRPC, rule: str = '/api'): def api_init(app, jsonrpc: JSONRPC, rule: str = '/api'):
@@ -38,6 +56,6 @@ def api_init(app, jsonrpc: JSONRPC, rule: str = '/api'):
pathlib.Path(__file__).parent.resolve() / 'templates' pathlib.Path(__file__).parent.resolve() / 'templates'
) )
) )
handler = APIView(jsonrpc) handler = APIHandler(jsonrpc)
app.router.add_route('GET', rule, handler.get) app.router.add_route('GET', rule, handler.get)
app.router.add_route('POST', rule, handler.post) app.router.add_route('POST', rule, handler.post)

View File

@@ -6,6 +6,7 @@ from flask.views import MethodView
from flask import jsonify, Response, request, render_template from flask import jsonify, Response, request, render_template
from ..exceptions import ParseError from ..exceptions import ParseError
from ..serialize import JSONRPCEncoder
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -20,9 +21,13 @@ def to_json(
try: try:
return json.loads(request_data) return json.loads(request_data)
except ValueError as e: except ValueError as e:
print('invalid json:', request_data)
log.error('invalid json: %s', request_data) log.error('invalid json: %s', request_data)
log.exception(e) log.exception(e)
raise ParseError(data={'message': 'Invalid JSON: {0!r}'.format(request_data)}) result = ParseError(
message={'message': 'Invalid JSON: {0!r}'.format(request_data)}
)
return result
# raise ValueError('Invalid JSON') # raise ValueError('Invalid JSON')
@@ -43,8 +48,12 @@ class APIView(MethodView):
return body return body
def post(self): def post(self):
if self.debug:
print('request.data', request.data)
json_data = to_json(request.data) json_data = to_json(request.data)
result = self.jsonrpc(json_data) result = self.jsonrpc(json_data)
if self.debug: if self.debug:
print(result)
log.debug(result) log.debug(result)
return jsonify(result) # return jsonify(result)
return json.dumps(result, cls=JSONRPCEncoder)

View File

@@ -30,12 +30,14 @@ class Client:
self.url, self.url,
data=payload, data=payload,
headers=self.headers headers=self.headers
).json() )
print('content', response.content)
result = response.json()
assert 'jsonrpc' in response assert 'jsonrpc' in result
assert 'id' in response assert 'id' in result
assert response["jsonrpc"] in ['2.0', '3.0'] assert result["jsonrpc"] in ['2.0', '3.0']
if '3.0' in response["jsonrpc"]: if '3.0' in result["jsonrpc"]:
assert 'meta' in response assert 'meta' in result
return response return result

19
src/jsonrpc/serialize.py Normal file
View File

@@ -0,0 +1,19 @@
__author__ = 'RemiZOffAlex'
__email__ = 'remizoffalex@mail.ru'
import json
from datetime import datetime
from .exceptions import JSONRPCError
class JSONRPCEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
elif isinstance(obj, JSONRPCError):
return obj.__json__()
elif hasattr(obj, '__dict__'):
return obj.__dict__
return super().default(obj)