From 2a091b46ee9fedeead43689af098f8d5b8635c5c Mon Sep 17 00:00:00 2001 From: Wanchao Zhang Date: Wed, 11 Jul 2018 16:02:18 +0800 Subject: [PATCH 1/6] misc updates - Support custom json encoder for serializing response data - Support registering global decorators for jsonrpc methods - Support custom method prefix for Dispatcher.add_class - Support explicitly exporting subset of class methods with Dispatcher.add_class --- jsonrpc/backend/flask.py | 10 +++++---- jsonrpc/dispatcher.py | 35 ++++++++++++++++++++++++++++---- jsonrpc/tests/test_dispatcher.py | 4 ++-- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/jsonrpc/backend/flask.py b/jsonrpc/backend/flask.py index d73c273..943f175 100644 --- a/jsonrpc/backend/flask.py +++ b/jsonrpc/backend/flask.py @@ -19,7 +19,8 @@ class JSONRPCAPI(object): - def __init__(self, dispatcher=None, check_content_type=True): + def __init__(self, dispatcher=None, check_content_type=True, + json_encoder=None): """ :param dispatcher: methods dispatcher @@ -31,6 +32,7 @@ def __init__(self, dispatcher=None, check_content_type=True): self.dispatcher = dispatcher if dispatcher is not None \ else Dispatcher() self.check_content_type = check_content_type + self.json_encoder = json_encoder or DatetimeDecimalEncoder def as_blueprint(self, name=None): blueprint = Blueprint(name if name else str(uuid4()), __name__) @@ -83,9 +85,9 @@ def _get_request_str(self): return request.data return list(request.form.keys())[0] - @staticmethod - def _serialize(s): - return json.dumps(s, cls=DatetimeDecimalEncoder) + # @staticmethod + def _serialize(self, s): + return json.dumps(s, cls=self.json_encoder) api = JSONRPCAPI() diff --git a/jsonrpc/dispatcher.py b/jsonrpc/dispatcher.py index 2bf5e43..6dd0619 100644 --- a/jsonrpc/dispatcher.py +++ b/jsonrpc/dispatcher.py @@ -4,6 +4,7 @@ """ import collections +from functools import wraps class Dispatcher(collections.MutableMapping): @@ -27,6 +28,7 @@ def __init__(self, prototype=None): None """ + self._decorators = [] self.method_map = dict() if prototype is not None: @@ -36,7 +38,7 @@ def __getitem__(self, key): return self.method_map[key] def __setitem__(self, key, value): - self.method_map[key] = value + self.method_map[key] = self._wrap_method(value) def __delitem__(self, key): del self.method_map[key] @@ -50,8 +52,24 @@ def __iter__(self): def __repr__(self): return repr(self.method_map) + def register_decorator(self, a): + self._decorators.extend(a if hasattr(a, '__iter__') else [a]) + + def _wrap_method(self, f): + @wraps(f) + def _method(*args, **kwargs): + nf = f + for deco in reversed(self._decorators): + nf = deco(nf) + return nf(*args, **kwargs) + + return _method + def add_class(self, cls): - prefix = cls.__name__.lower() + '.' + if hasattr(cls, 'rpc_method_prefix'): + prefix = cls.rpc_method_prefix + '.' + else: + prefix = cls.__name__.lower() + '.' self.build_method_map(cls(), prefix) def add_object(self, obj): @@ -94,7 +112,7 @@ def mymethod(*args, **kwargs): print(args, kwargs) """ - self.method_map[name or f.__name__] = f + self[name or f.__name__] = f return f def build_method_map(self, prototype, prefix=''): @@ -112,10 +130,19 @@ def build_method_map(self, prototype, prefix=''): Prefix of methods """ + rpc_exports = getattr(prototype, 'rpc_exports', None) + + def _should_export(method): + if method.startswith('_'): + return False + if rpc_exports is None: + return True + return method in rpc_exports + if not isinstance(prototype, dict): prototype = dict((method, getattr(prototype, method)) for method in dir(prototype) - if not method.startswith('_')) + if _should_export(method)) for attr, method in prototype.items(): if callable(method): diff --git a/jsonrpc/tests/test_dispatcher.py b/jsonrpc/tests/test_dispatcher.py index 56876b2..b74f414 100644 --- a/jsonrpc/tests/test_dispatcher.py +++ b/jsonrpc/tests/test_dispatcher.py @@ -90,10 +90,10 @@ def test_to_dict(self): d = Dispatcher() def func(): - return "" + return "x" d["method"] = func - self.assertEqual(dict(d), {"method": func}) + self.assertEqual(dict(d)["method"](), "x") def test_init_from_object_instance(self): From 4425243ce2e6fbb459e3e5e6ea689993a267e6af Mon Sep 17 00:00:00 2001 From: Wanchao Zhang Date: Wed, 25 Jul 2018 15:51:16 +0800 Subject: [PATCH 2/6] Dispatcher.add_method as a decorator generator --- jsonrpc/dispatcher.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/jsonrpc/dispatcher.py b/jsonrpc/dispatcher.py index 6dd0619..fc462e9 100644 --- a/jsonrpc/dispatcher.py +++ b/jsonrpc/dispatcher.py @@ -3,6 +3,7 @@ For usage examples see :meth:`Dispatcher.add_method` """ +from . import six import collections from functools import wraps @@ -111,7 +112,20 @@ def add_method(self, f, name=None): def mymethod(*args, **kwargs): print(args, kwargs) + >>> @d.add_method(name='MyMethod') + def mymethod(*args, **kwargs): + print(args, kwargs) + """ + if isinstance(f, six.string_types): + name, f = f, name + + if f is None: + # Be decorator generator + def _add_method(f): + return self.add_method(f, name) + return _add_method + self[name or f.__name__] = f return f From 8925a08af3a77ffec133c9c9dd630cfcff6b11db Mon Sep 17 00:00:00 2001 From: Wanchao Zhang Date: Thu, 26 Jul 2018 17:16:14 +0800 Subject: [PATCH 3/6] fix a bug of Dispatcher.add_class when using as decorator Below is the code to reveal this bug: @dispatcher.add_class class ProcessOrderAPI(APISet): rpc_exports = ['query', 'detail', 'start', 'update'] rpc_method_prefix = 'ProcessOrder' Model = ProcessOrder def update(self, object, attrs): # NOTE HERE: Since add_class retuns None previously, # So the 1st argument of super() is None return super(ProcessOrderAPI, self).update(object, attrs) --- jsonrpc/dispatcher.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jsonrpc/dispatcher.py b/jsonrpc/dispatcher.py index fc462e9..5687d8f 100644 --- a/jsonrpc/dispatcher.py +++ b/jsonrpc/dispatcher.py @@ -73,6 +73,8 @@ def add_class(self, cls): prefix = cls.__name__.lower() + '.' self.build_method_map(cls(), prefix) + return cls # for working as decorator + def add_object(self, obj): prefix = obj.__class__.__name__.lower() + '.' self.build_method_map(obj, prefix) From fdae122e6c96acc363457fdf15b5173272c8306b Mon Sep 17 00:00:00 2001 From: Wanchao Zhang Date: Mon, 5 Nov 2018 18:06:30 +0800 Subject: [PATCH 4/6] before_request hooks executed in jsonrpc handler --- jsonrpc/dispatcher.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/jsonrpc/dispatcher.py b/jsonrpc/dispatcher.py index 5687d8f..eb428f7 100644 --- a/jsonrpc/dispatcher.py +++ b/jsonrpc/dispatcher.py @@ -31,6 +31,7 @@ def __init__(self, prototype=None): """ self._decorators = [] self.method_map = dict() + self._before_request_hooks = [] if prototype is not None: self.build_method_map(prototype) @@ -56,9 +57,15 @@ def __repr__(self): def register_decorator(self, a): self._decorators.extend(a if hasattr(a, '__iter__') else [a]) + def before_request(self, hook): + self._before_request_hooks.append(hook) + def _wrap_method(self, f): @wraps(f) def _method(*args, **kwargs): + for hook in self._before_request_hooks: + hook() + nf = f for deco in reversed(self._decorators): nf = deco(nf) From 48f78077772d7b7a7adea69052f6a325ec25202d Mon Sep 17 00:00:00 2001 From: Wanchao Zhang Date: Wed, 5 Dec 2018 16:00:56 +0800 Subject: [PATCH 5/6] errorhandler for convert python excepiton to jsonrpc error --- jsonrpc/dispatcher.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/jsonrpc/dispatcher.py b/jsonrpc/dispatcher.py index eb428f7..927298b 100644 --- a/jsonrpc/dispatcher.py +++ b/jsonrpc/dispatcher.py @@ -32,6 +32,7 @@ def __init__(self, prototype=None): self._decorators = [] self.method_map = dict() self._before_request_hooks = [] + self._error_handler_spec = {} if prototype is not None: self.build_method_map(prototype) @@ -60,6 +61,12 @@ def register_decorator(self, a): def before_request(self, hook): self._before_request_hooks.append(hook) + def errorhandler(self, exception): + def decorator(f): + self._error_handler_spec[exception] = f + return f + return decorator + def _wrap_method(self, f): @wraps(f) def _method(*args, **kwargs): @@ -69,7 +76,14 @@ def _method(*args, **kwargs): nf = f for deco in reversed(self._decorators): nf = deco(nf) - return nf(*args, **kwargs) + + try: + return nf(*args, **kwargs) + except Exception as e: + for E, h in self._error_handler_spec.items(): + if isinstance(e, E): + return h(e) + raise return _method From 4c13c487676043c27425abc52f87a502eb57a8a9 Mon Sep 17 00:00:00 2001 From: Wanchao Zhang Date: Mon, 10 Dec 2018 10:33:12 +0800 Subject: [PATCH 6/6] bugfix for errorhandler --- jsonrpc/dispatcher.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jsonrpc/dispatcher.py b/jsonrpc/dispatcher.py index 927298b..2b42838 100644 --- a/jsonrpc/dispatcher.py +++ b/jsonrpc/dispatcher.py @@ -70,14 +70,14 @@ def decorator(f): def _wrap_method(self, f): @wraps(f) def _method(*args, **kwargs): - for hook in self._before_request_hooks: - hook() + try: + for hook in self._before_request_hooks: + hook() - nf = f - for deco in reversed(self._decorators): - nf = deco(nf) + nf = f + for deco in reversed(self._decorators): + nf = deco(nf) - try: return nf(*args, **kwargs) except Exception as e: for E, h in self._error_handler_spec.items():