Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow converting request['param'] to a JSON object if the only param given is a dictionary #29

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion jsonrpclib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 13 additions & 3 deletions jsonrpclib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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'
Expand All @@ -30,7 +30,17 @@ 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
# 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):
if not cls._instance:
Expand Down
84 changes: 49 additions & 35 deletions jsonrpclib/jsonrpc.py
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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')
Expand Down Expand Up @@ -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
Expand All @@ -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. """
Expand Down Expand Up @@ -157,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)
Expand All @@ -179,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:
Expand Down Expand Up @@ -241,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
Expand All @@ -265,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 ' +
Expand All @@ -288,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 = []
Expand All @@ -313,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

Expand All @@ -330,7 +333,7 @@ def __getattr__(self, name):
return new_job

class MultiCallIterator(object):

def __init__(self, results):
self.results = results

Expand All @@ -348,7 +351,7 @@ def __len__(self):
return len(self.results)

class MultiCall(object):

def __init__(self, server):
self._server = server
self._job_list = []
Expand Down Expand Up @@ -376,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

Expand Down Expand Up @@ -414,15 +417,22 @@ 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.')
if not self.id:
self.id = random_id()
request = { 'id':self.id, 'method':method }
if params:
request['params'] = 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
if self.version >= 2:
request['jsonrpc'] = str(self.version)
return request
Expand Down Expand Up @@ -452,10 +462,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:
Expand All @@ -464,7 +474,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.
"""
Expand Down Expand Up @@ -505,7 +515,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:
Expand All @@ -526,7 +536,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):
Expand Down