From 774c9353915f1b929e4a9c055ab2654c9c08c5d0 Mon Sep 17 00:00:00 2001 From: Tuomas Salo Date: Thu, 11 Apr 2013 17:01:43 +0300 Subject: [PATCH 1/5] In case of a non-pre-defined error, raise an AppError and give access to error.data --- jsonrpclib/__init__.py | 2 +- jsonrpclib/jsonrpc.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/jsonrpclib/__init__.py b/jsonrpclib/__init__.py index 6e884b8..86dbd27 100644 --- a/jsonrpclib/__init__.py +++ b/jsonrpclib/__init__.py @@ -3,4 +3,4 @@ from jsonrpclib.history import History history = History.instance() from jsonrpclib.jsonrpc import Server, MultiCall, Fault -from jsonrpclib.jsonrpc import ProtocolError, loads, dumps +from jsonrpclib.jsonrpc import ProtocolError, AppError, loads, dumps diff --git a/jsonrpclib/jsonrpc.py b/jsonrpclib/jsonrpc.py index e11939a..1e4619f 100644 --- a/jsonrpclib/jsonrpc.py +++ b/jsonrpclib/jsonrpc.py @@ -109,6 +109,9 @@ def jloads(json_string): class ProtocolError(Exception): pass +class AppError(ProtocolError): + def data(self): + return self[0][2] class TransportMixIn(object): """ Just extends the XMLRPC transport where necessary. """ @@ -526,7 +529,11 @@ def check_for_errors(result): if 'error' in result.keys() and result['error'] != None: code = result['error']['code'] message = result['error']['message'] - raise ProtocolError((code, message)) + if -32700 <= code <= -32000: + # pre-defined errors, see http://www.jsonrpc.org/specification#error_object + raise ProtocolError((code, message)) + else: + raise AppError((code, message, result['error'].get('data', None))) return result def isbatch(result): From 7fa129dad4f670659a97b0eb1ced714c451a8cde Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Fri, 25 Apr 2014 09:35:45 +0200 Subject: [PATCH 2/5] Set request['params'] to a dictionary type if the only parameter is a dictionary --- jsonrpclib/jsonrpc.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jsonrpclib/jsonrpc.py b/jsonrpclib/jsonrpc.py index e11939a..dfce03d 100644 --- a/jsonrpclib/jsonrpc.py +++ b/jsonrpclib/jsonrpc.py @@ -422,7 +422,10 @@ def request(self, method, params=[]): self.id = random_id() request = { 'id':self.id, 'method':method } if params: - request['params'] = params + if (len(params) == 1) and isinstance(params[0], dict): + request['params'] = params[0] + else: + request['params'] = params if self.version >= 2: request['jsonrpc'] = str(self.version) return request From e753f3fc40cb3c60f916989ef79382477bb67835 Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Fri, 25 Apr 2014 09:52:59 +0200 Subject: [PATCH 3/5] Add config parameter for dictionary conversion in request --- jsonrpclib/config.py | 6 +++++- jsonrpclib/jsonrpc.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/jsonrpclib/config.py b/jsonrpclib/config.py index 4d28f1b..8d1506c 100644 --- a/jsonrpclib/config.py +++ b/jsonrpclib/config.py @@ -30,7 +30,11 @@ class Config(object): '.'.join([str(ver) for ver in sys.version_info[0:3]]) # User agent to use for calls. _instance = None - + # If there is only one parameter and that is a dictionary, convert the + # whole params member to that dictionary, instead of passing an array with + # the dictionary as its only element + convert_only_dict = False + @classmethod def instance(cls): if not cls._instance: diff --git a/jsonrpclib/jsonrpc.py b/jsonrpclib/jsonrpc.py index dfce03d..3e9874c 100644 --- a/jsonrpclib/jsonrpc.py +++ b/jsonrpclib/jsonrpc.py @@ -414,7 +414,7 @@ def __init__(self, rpcid=None, version=None): version = config.version self.id = rpcid self.version = float(version) - + def request(self, method, params=[]): if type(method) not in types.StringTypes: raise ValueError('Method name must be a string.') @@ -422,7 +422,7 @@ def request(self, method, params=[]): self.id = random_id() request = { 'id':self.id, 'method':method } if params: - if (len(params) == 1) and isinstance(params[0], dict): + if (len(params) == 1) and isinstance(params[0], dict) and config.convert_only_dict: request['params'] = params[0] else: request['params'] = params From 5fda31413e5cb7945f17922e0ffe150b4b359295 Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Fri, 25 Apr 2014 12:52:55 +0200 Subject: [PATCH 4/5] Remove trailing spaces --- jsonrpclib/config.py | 4 +-- jsonrpclib/jsonrpc.py | 64 +++++++++++++++++++++---------------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/jsonrpclib/config.py b/jsonrpclib/config.py index 8d1506c..c4c32a0 100644 --- a/jsonrpclib/config.py +++ b/jsonrpclib/config.py @@ -6,7 +6,7 @@ def add(self, cls): class Config(object): """ - This is pretty much used exclusively for the 'jsonclass' + This is pretty much used exclusively for the 'jsonclass' functionality... set use_jsonclass to False to turn it off. You can change serialize_method and ignore_attribute, or use the local_classes.add(class) to include "local" classes. @@ -15,7 +15,7 @@ class Config(object): # Change to False to keep __jsonclass__ entries raw. serialize_method = '_serialize' # The serialize_method should be a string that references the - # method on a custom class object which is responsible for + # method on a custom class object which is responsible for # returning a tuple of the constructor arguments and a dict of # attributes. ignore_attribute = '_ignore' diff --git a/jsonrpclib/jsonrpc.py b/jsonrpclib/jsonrpc.py index 9d9bbf1..e36b3ee 100644 --- a/jsonrpclib/jsonrpc.py +++ b/jsonrpclib/jsonrpc.py @@ -1,15 +1,15 @@ """ -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ============================ JSONRPC Library (jsonrpclib) @@ -29,7 +29,7 @@ and other things to tie the thing off nicely. :) For a quick-start, just open a console and type the following, -replacing the server address, method, and parameters +replacing the server address, method, and parameters appropriately. >>> import jsonrpclib >>> server = jsonrpclib.Server('http://localhost:8181') @@ -81,8 +81,8 @@ IDCHARS = string.ascii_lowercase+string.digits class UnixSocketMissing(Exception): - """ - Just a properly named Exception if Unix Sockets usage is + """ + Just a properly named Exception if Unix Sockets usage is attempted on a platform that doesn't support them (Windows) """ pass @@ -160,14 +160,14 @@ class SafeTransport(TransportMixIn, XMLSafeTransport): USE_UNIX_SOCKETS = False -try: +try: from socket import AF_UNIX, SOCK_STREAM USE_UNIX_SOCKETS = True except ImportError: pass - + if (USE_UNIX_SOCKETS): - + class UnixHTTPConnection(HTTPConnection): def connect(self): self.sock = socket(AF_UNIX, SOCK_STREAM) @@ -182,14 +182,14 @@ def make_connection(self, host): host, extra_headers, x509 = self.get_host_info(host) return UnixHTTP(host) - + class ServerProxy(XMLServerProxy): """ Unfortunately, much more of this class has to be copied since so much of it does the serialization. """ - def __init__(self, uri, transport=None, encoding=None, + def __init__(self, uri, transport=None, encoding=None, verbose=0, version=None): import urllib if not version: @@ -244,13 +244,13 @@ def _run_request(self, request, notify=None): request, verbose=self.__verbose ) - + # Here, the XMLRPC library translates a single list # response to the single value -- should we do the # same, and require a tuple / list to be passed to - # the response object, or expect the Server to be + # the response object, or expect the Server to be # outputting the response appropriately? - + history.add_response(response) if not response: return None @@ -268,7 +268,7 @@ def _notify(self): class _Method(XML_Method): - + def __call__(self, *args, **kwargs): if len(args) > 0 and len(kwargs) > 0: raise ProtocolError('Cannot use both positional ' + @@ -291,11 +291,11 @@ def __init__(self, request): def __getattr__(self, name): return _Method(self._request, name) - + # Batch implementation class MultiCallMethod(object): - + def __init__(self, method, notify=False): self.method = method self.params = [] @@ -316,14 +316,14 @@ def request(self, encoding=None, rpcid=None): def __repr__(self): return '%s' % self.request() - + def __getattr__(self, method): new_method = '%s.%s' % (self.method, method) self.method = new_method return self class MultiCallNotify(object): - + def __init__(self, multicall): self.multicall = multicall @@ -333,7 +333,7 @@ def __getattr__(self, name): return new_job class MultiCallIterator(object): - + def __init__(self, results): self.results = results @@ -351,7 +351,7 @@ def __len__(self): return len(self.results) class MultiCall(object): - + def __init__(self, server): self._server = server self._job_list = [] @@ -379,7 +379,7 @@ def __getattr__(self, name): __call__ = _request -# These lines conform to xmlrpclib's "compatibility" line. +# These lines conform to xmlrpclib's "compatibility" line. # Not really sure if we should include these, but oh well. Server = ServerProxy @@ -458,10 +458,10 @@ def error(self, code=-32000, message='Server error.'): error['error'] = {'code':code, 'message':message} return error -def dumps(params=[], methodname=None, methodresponse=None, +def dumps(params=[], methodname=None, methodresponse=None, encoding=None, rpcid=None, version=None, notify=None): """ - This differs from the Python implementation in that it implements + This differs from the Python implementation in that it implements the rpcid argument since the 2.0 spec requires it for responses. """ if not version: @@ -470,7 +470,7 @@ def dumps(params=[], methodname=None, methodresponse=None, if methodname in types.StringTypes and \ type(params) not in valid_params and \ not isinstance(params, Fault): - """ + """ If a method, and params are not in a listish or a Fault, error out. """ @@ -511,7 +511,7 @@ def loads(data): # notification return None result = jloads(data) - # if the above raises an error, the implementing server code + # if the above raises an error, the implementing server code # should return something like the following: # { 'jsonrpc':'2.0', 'error': fault.error(), id: None } if config.use_jsonclass == True: From 3b0b9f7d13e95d7afcc20eec1becf1d31135da2b Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Fri, 25 Apr 2014 12:53:53 +0200 Subject: [PATCH 5/5] Add possibility to set an authentication token This currently works only if params is a dictionary --- jsonrpclib/config.py | 6 ++++++ jsonrpclib/jsonrpc.py | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/jsonrpclib/config.py b/jsonrpclib/config.py index c4c32a0..643d196 100644 --- a/jsonrpclib/config.py +++ b/jsonrpclib/config.py @@ -34,6 +34,12 @@ class Config(object): # whole params member to that dictionary, instead of passing an array with # the dictionary as its only element convert_only_dict = False + # If set to True, if the params is a dictionary, and has an authentication + # token (set in config.auth_token_name), that will be added to the request + # as request['auth'] + extract_auth_token = False + # The name to be extracted as the authentication token + auth_token_name = None @classmethod def instance(cls): diff --git a/jsonrpclib/jsonrpc.py b/jsonrpclib/jsonrpc.py index e36b3ee..283c00c 100644 --- a/jsonrpclib/jsonrpc.py +++ b/jsonrpclib/jsonrpc.py @@ -426,6 +426,10 @@ def request(self, method, params=[]): request = { 'id':self.id, 'method':method } if params: if (len(params) == 1) and isinstance(params[0], dict) and config.convert_only_dict: + if config.extract_auth_token and config.auth_token_name: + if config.auth_token_name in params[0]: + request[config.auth_token_name] = params[0][config.auth_token_name] + del params[0][config.auth_token_name] request['params'] = params[0] else: request['params'] = params