Merge pull request #40 from joshmarshall/travis-and-pip
Adding Travis for testing, fixing minor PIP violations.
This commit is contained in:
commit
10bea44bbe
|
@ -1,3 +1,4 @@
|
|||
*.pyc
|
||||
build/*
|
||||
dist/*
|
||||
.coverage
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
language: python
|
||||
sudo: false
|
||||
python:
|
||||
- '2.7'
|
||||
- '2.6'
|
||||
install:
|
||||
- pip install -r dev-requirements.txt
|
||||
# will need this when we automatically push to pypi
|
||||
# - pip install twine
|
||||
script: nosetests tests.py --with-coverage --cover-package=jsonrpclib
|
|
@ -1,3 +1,5 @@
|
|||
[![Build Status](https://travis-ci.org/joshmarshall/jsonrpclib.svg)](https://travis-ci.org/joshmarshall/jsonrpclib)
|
||||
|
||||
JSONRPClib
|
||||
==========
|
||||
This library is an implementation of the JSON-RPC specification.
|
||||
|
@ -211,7 +213,8 @@ TESTS
|
|||
I've dropped almost-verbatim tests from the JSON-RPC spec 2.0 page.
|
||||
You can run it with:
|
||||
|
||||
python tests.py
|
||||
pip install -r dev-requirements.txt
|
||||
nosetests tests.py
|
||||
|
||||
TODO
|
||||
----
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
coverage==4.0
|
||||
linecache2==1.0.0
|
||||
nose==1.3.7
|
||||
six==1.9.0
|
||||
traceback2==1.4.0
|
||||
unittest2==1.1.0
|
|
@ -15,6 +15,7 @@ except ImportError:
|
|||
# For Windows
|
||||
fcntl = None
|
||||
|
||||
|
||||
def get_version(request):
|
||||
# must be a dict
|
||||
if 'jsonrpc' in request.keys():
|
||||
|
@ -22,9 +23,10 @@ def get_version(request):
|
|||
if 'id' in request.keys():
|
||||
return 1.0
|
||||
return None
|
||||
|
||||
|
||||
|
||||
def validate_request(request):
|
||||
if type(request) is not types.DictType:
|
||||
if not isinstance(request, dict):
|
||||
fault = Fault(
|
||||
-32600, 'Request must be {}, not %s.' % type(request)
|
||||
)
|
||||
|
@ -33,27 +35,27 @@ def validate_request(request):
|
|||
version = get_version(request)
|
||||
if not version:
|
||||
fault = Fault(-32600, 'Request %s invalid.' % request, rpcid=rpcid)
|
||||
return fault
|
||||
return fault
|
||||
request.setdefault('params', [])
|
||||
method = request.get('method', None)
|
||||
params = request.get('params')
|
||||
param_types = (types.ListType, types.DictType, types.TupleType)
|
||||
if not method or type(method) not in types.StringTypes or \
|
||||
type(params) not in param_types:
|
||||
type(params) not in param_types:
|
||||
fault = Fault(
|
||||
-32600, 'Invalid request parameters or method.', rpcid=rpcid
|
||||
)
|
||||
return fault
|
||||
return True
|
||||
|
||||
|
||||
class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
|
||||
|
||||
def __init__(self, encoding=None):
|
||||
SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self,
|
||||
allow_none=True,
|
||||
encoding=encoding)
|
||||
SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(
|
||||
self, allow_none=True, encoding=encoding)
|
||||
|
||||
def _marshaled_dispatch(self, data, dispatch_method = None):
|
||||
def _marshaled_dispatch(self, data, dispatch_method=None):
|
||||
response = None
|
||||
try:
|
||||
request = jsonrpclib.loads(data)
|
||||
|
@ -64,7 +66,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
|
|||
if not request:
|
||||
fault = Fault(-32600, 'Request invalid -- no request data.')
|
||||
return fault.response()
|
||||
if type(request) is types.ListType:
|
||||
if isinstance(request, list):
|
||||
# This SHOULD be a batch, by spec
|
||||
responses = []
|
||||
for req_entry in request:
|
||||
|
@ -79,7 +81,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
|
|||
response = '[%s]' % ','.join(responses)
|
||||
else:
|
||||
response = ''
|
||||
else:
|
||||
else:
|
||||
result = validate_request(request)
|
||||
if type(result) is Fault:
|
||||
return result.response()
|
||||
|
@ -99,7 +101,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
|
|||
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||
fault = Fault(-32603, '%s:%s' % (exc_type, exc_value))
|
||||
return fault.response()
|
||||
if 'id' not in request.keys() or request['id'] == None:
|
||||
if 'id' not in request.keys() or request['id'] is None:
|
||||
# It's a notification
|
||||
return None
|
||||
try:
|
||||
|
@ -132,25 +134,26 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
|
|||
pass
|
||||
if func is not None:
|
||||
try:
|
||||
if type(params) is types.ListType:
|
||||
if isinstance(params, types.ListType):
|
||||
response = func(*params)
|
||||
else:
|
||||
response = func(**params)
|
||||
return response
|
||||
except TypeError:
|
||||
return Fault(-32602, 'Invalid parameters.')
|
||||
# except TypeError:
|
||||
# return Fault(-32602, 'Invalid parameters.')
|
||||
except:
|
||||
err_lines = traceback.format_exc().splitlines()
|
||||
trace_string = '%s | %s' % (err_lines[-3], err_lines[-1])
|
||||
fault = jsonrpclib.Fault(-32603, 'Server error: %s' %
|
||||
fault = jsonrpclib.Fault(-32603, 'Server error: %s' %
|
||||
trace_string)
|
||||
return fault
|
||||
else:
|
||||
return Fault(-32601, 'Method %s not supported.' % method)
|
||||
|
||||
|
||||
class SimpleJSONRPCRequestHandler(
|
||||
SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
|
||||
|
||||
|
||||
def do_POST(self):
|
||||
if not self.is_rpc_path_valid():
|
||||
self.report_404()
|
||||
|
@ -166,13 +169,13 @@ class SimpleJSONRPCRequestHandler(
|
|||
data = ''.join(L)
|
||||
response = self.server._marshaled_dispatch(data)
|
||||
self.send_response(200)
|
||||
except Exception, e:
|
||||
except Exception:
|
||||
self.send_response(500)
|
||||
err_lines = traceback.format_exc().splitlines()
|
||||
trace_string = '%s | %s' % (err_lines[-3], err_lines[-1])
|
||||
fault = jsonrpclib.Fault(-32603, 'Server error: %s' % trace_string)
|
||||
response = fault.response()
|
||||
if response == None:
|
||||
if response is None:
|
||||
response = ''
|
||||
self.send_header("Content-type", "application/json-rpc")
|
||||
self.send_header("Content-length", str(len(response)))
|
||||
|
@ -181,6 +184,7 @@ class SimpleJSONRPCRequestHandler(
|
|||
self.wfile.flush()
|
||||
self.connection.shutdown(1)
|
||||
|
||||
|
||||
class SimpleJSONRPCServer(SocketServer.TCPServer, SimpleJSONRPCDispatcher):
|
||||
|
||||
allow_reuse_address = True
|
||||
|
@ -198,7 +202,7 @@ class SimpleJSONRPCServer(SocketServer.TCPServer, SimpleJSONRPCDispatcher):
|
|||
# Unix sockets can't be bound if they already exist in the
|
||||
# filesystem. The convention of e.g. X11 is to unlink
|
||||
# before binding again.
|
||||
if os.path.exists(addr):
|
||||
if os.path.exists(addr):
|
||||
try:
|
||||
os.unlink(addr)
|
||||
except OSError:
|
||||
|
@ -207,13 +211,14 @@ class SimpleJSONRPCServer(SocketServer.TCPServer, SimpleJSONRPCDispatcher):
|
|||
if vi[0] < 3 and vi[1] < 6:
|
||||
SocketServer.TCPServer.__init__(self, addr, requestHandler)
|
||||
else:
|
||||
SocketServer.TCPServer.__init__(self, addr, requestHandler,
|
||||
bind_and_activate)
|
||||
SocketServer.TCPServer.__init__(
|
||||
self, addr, requestHandler, bind_and_activate)
|
||||
if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
|
||||
flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
|
||||
flags |= fcntl.FD_CLOEXEC
|
||||
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
|
||||
|
||||
|
||||
class CGIJSONRPCRequestHandler(SimpleJSONRPCDispatcher):
|
||||
|
||||
def __init__(self, encoding=None):
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import sys
|
||||
|
||||
|
||||
class LocalClasses(dict):
|
||||
def add(self, cls):
|
||||
self[cls.__name__] = 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 +17,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'
|
||||
|
@ -30,7 +32,7 @@ class Config(object):
|
|||
'.'.join([str(ver) for ver in sys.version_info[0:3]])
|
||||
# User agent to use for calls.
|
||||
_instance = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if not cls._instance:
|
||||
|
|
|
@ -2,13 +2,13 @@ class History(object):
|
|||
"""
|
||||
This holds all the response and request objects for a
|
||||
session. A server using this should call "clear" after
|
||||
each request cycle in order to keep it from clogging
|
||||
each request cycle in order to keep it from clogging
|
||||
memory.
|
||||
"""
|
||||
requests = []
|
||||
responses = []
|
||||
_instance = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if not cls._instance:
|
||||
|
@ -17,7 +17,7 @@ class History(object):
|
|||
|
||||
def add_response(self, response_obj):
|
||||
self.responses.append(response_obj)
|
||||
|
||||
|
||||
def add_request(self, request_obj):
|
||||
self.requests.append(request_obj)
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import types
|
||||
import inspect
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from jsonrpclib import config
|
||||
|
||||
|
@ -30,9 +29,11 @@ value_types = [
|
|||
supported_types = iter_types+string_types+numeric_types+value_types
|
||||
invalid_module_chars = r'[^a-zA-Z0-9\_\.]'
|
||||
|
||||
|
||||
class TranslationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def dump(obj, serialize_method=None, ignore_attribute=None, ignore=[]):
|
||||
if not serialize_method:
|
||||
serialize_method = config.serialize_method
|
||||
|
@ -46,17 +47,17 @@ def dump(obj, serialize_method=None, ignore_attribute=None, ignore=[]):
|
|||
if obj_type in (types.ListType, types.TupleType):
|
||||
new_obj = []
|
||||
for item in obj:
|
||||
new_obj.append(dump(item, serialize_method,
|
||||
ignore_attribute, ignore))
|
||||
if obj_type is types.TupleType:
|
||||
new_obj.append(
|
||||
dump(item, serialize_method, ignore_attribute, ignore))
|
||||
if isinstance(obj_type, types.TupleType):
|
||||
new_obj = tuple(new_obj)
|
||||
return new_obj
|
||||
# It's a dict...
|
||||
else:
|
||||
new_obj = {}
|
||||
for key, value in obj.iteritems():
|
||||
new_obj[key] = dump(value, serialize_method,
|
||||
ignore_attribute, ignore)
|
||||
new_obj[key] = dump(
|
||||
value, serialize_method, ignore_attribute, ignore)
|
||||
return new_obj
|
||||
# It's not a standard type, so it needs __jsonclass__
|
||||
module_name = inspect.getmodule(obj).__name__
|
||||
|
@ -64,7 +65,7 @@ def dump(obj, serialize_method=None, ignore_attribute=None, ignore=[]):
|
|||
json_class = class_name
|
||||
if module_name not in ['', '__main__']:
|
||||
json_class = '%s.%s' % (module_name, json_class)
|
||||
return_obj = {"__jsonclass__":[json_class,]}
|
||||
return_obj = {"__jsonclass__": [json_class]}
|
||||
# If a serialization method is defined..
|
||||
if serialize_method in dir(obj):
|
||||
# Params can be a dict (keyword) or list (positional)
|
||||
|
@ -84,21 +85,23 @@ def dump(obj, serialize_method=None, ignore_attribute=None, ignore=[]):
|
|||
if type(attr_value) in supported_types and \
|
||||
attr_name not in ignore_list and \
|
||||
attr_value not in ignore_list:
|
||||
attrs[attr_name] = dump(attr_value, serialize_method,
|
||||
ignore_attribute, ignore)
|
||||
attrs[attr_name] = dump(
|
||||
attr_value, serialize_method, ignore_attribute, ignore)
|
||||
return_obj.update(attrs)
|
||||
return return_obj
|
||||
|
||||
|
||||
def load(obj):
|
||||
if type(obj) in string_types+numeric_types+value_types:
|
||||
if type(obj) in string_types + numeric_types + value_types:
|
||||
return obj
|
||||
if type(obj) is types.ListType:
|
||||
|
||||
if isinstance(obj, list):
|
||||
return_list = []
|
||||
for entry in obj:
|
||||
return_list.append(load(entry))
|
||||
return return_list
|
||||
# Othewise, it's a dict type
|
||||
if '__jsonclass__' not in obj.keys():
|
||||
if '__jsonclass__' not in obj:
|
||||
return_dict = {}
|
||||
for key, value in obj.iteritems():
|
||||
new_value = load(value)
|
||||
|
@ -139,9 +142,9 @@ def load(obj):
|
|||
json_class = getattr(temp_module, json_class_name)
|
||||
# Creating the object...
|
||||
new_obj = None
|
||||
if type(params) is types.ListType:
|
||||
if isinstance(params, list):
|
||||
new_obj = json_class(*params)
|
||||
elif type(params) is types.DictType:
|
||||
elif isinstance(params, dict):
|
||||
new_obj = json_class(**params)
|
||||
else:
|
||||
raise TranslationError('Constructor args must be a dict or list.')
|
||||
|
|
|
@ -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 @@ Eventually, I'll add a SimpleXMLRPCServer compatible library,
|
|||
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')
|
||||
|
@ -47,17 +47,14 @@ See http://code.google.com/p/jsonrpclib/ for more info.
|
|||
"""
|
||||
|
||||
import types
|
||||
import sys
|
||||
from xmlrpclib import Transport as XMLTransport
|
||||
from xmlrpclib import SafeTransport as XMLSafeTransport
|
||||
from xmlrpclib import ServerProxy as XMLServerProxy
|
||||
from xmlrpclib import _Method as XML_Method
|
||||
import time
|
||||
import string
|
||||
import random
|
||||
|
||||
# Library includes
|
||||
import jsonrpclib
|
||||
from jsonrpclib import config
|
||||
from jsonrpclib import history
|
||||
|
||||
|
@ -80,14 +77,17 @@ except ImportError:
|
|||
|
||||
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
|
||||
|
||||
#JSON Abstractions
|
||||
|
||||
# JSON Abstractions
|
||||
|
||||
|
||||
def jdumps(obj, encoding='utf-8'):
|
||||
# Do 'serialize' test at some point for other classes
|
||||
|
@ -97,6 +97,7 @@ def jdumps(obj, encoding='utf-8'):
|
|||
else:
|
||||
return json.dumps(obj, encoding=encoding)
|
||||
|
||||
|
||||
def jloads(json_string):
|
||||
global cjson
|
||||
if cjson:
|
||||
|
@ -107,14 +108,17 @@ def jloads(json_string):
|
|||
|
||||
# XMLRPClib re-implementations
|
||||
|
||||
|
||||
class ProtocolError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TransportMixIn(object):
|
||||
""" Just extends the XMLRPC transport where necessary. """
|
||||
user_agent = config.user_agent
|
||||
# for Python 2.7 support
|
||||
_connection = None
|
||||
_connection = (None, None)
|
||||
_extra_headers = []
|
||||
|
||||
def send_content(self, connection, request_body):
|
||||
connection.putheader("Content-Type", "application/json-rpc")
|
||||
|
@ -127,6 +131,7 @@ class TransportMixIn(object):
|
|||
target = JSONTarget()
|
||||
return JSONParser(target), target
|
||||
|
||||
|
||||
class JSONParser(object):
|
||||
def __init__(self, target):
|
||||
self.target = target
|
||||
|
@ -137,6 +142,7 @@ class JSONParser(object):
|
|||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
class JSONTarget(object):
|
||||
def __init__(self):
|
||||
self.data = []
|
||||
|
@ -147,11 +153,13 @@ class JSONTarget(object):
|
|||
def close(self):
|
||||
return ''.join(self.data)
|
||||
|
||||
|
||||
class Transport(TransportMixIn, XMLTransport):
|
||||
def __init__(self):
|
||||
TransportMixIn.__init__(self)
|
||||
XMLTransport.__init__(self)
|
||||
|
||||
|
||||
class SafeTransport(TransportMixIn, XMLSafeTransport):
|
||||
def __init__(self):
|
||||
TransportMixIn.__init__(self)
|
||||
|
@ -162,14 +170,14 @@ from socket import socket
|
|||
|
||||
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)
|
||||
|
@ -179,19 +187,19 @@ if (USE_UNIX_SOCKETS):
|
|||
_connection_class = UnixHTTPConnection
|
||||
|
||||
class UnixTransport(TransportMixIn, XMLTransport):
|
||||
|
||||
def make_connection(self, host):
|
||||
import httplib
|
||||
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:
|
||||
|
@ -210,7 +218,7 @@ class ServerProxy(XMLServerProxy):
|
|||
self.__host, self.__handler = urllib.splithost(uri)
|
||||
if not self.__handler:
|
||||
# Not sure if this is in the JSON spec?
|
||||
#self.__handler = '/'
|
||||
# self.__handler = '/'
|
||||
self.__handler == '/'
|
||||
if transport is None:
|
||||
if schema == 'unix':
|
||||
|
@ -246,13 +254,13 @@ class ServerProxy(XMLServerProxy):
|
|||
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
|
||||
|
@ -270,11 +278,12 @@ class ServerProxy(XMLServerProxy):
|
|||
|
||||
|
||||
class _Method(XML_Method):
|
||||
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if len(args) > 0 and len(kwargs) > 0:
|
||||
raise ProtocolError('Cannot use both positional ' +
|
||||
'and keyword arguments (according to JSON-RPC spec.)')
|
||||
raise ProtocolError(
|
||||
'Cannot use both positional and keyword arguments '
|
||||
'(according to JSON-RPC spec.)')
|
||||
if len(args) > 0:
|
||||
return self.__send(self.__name, args)
|
||||
else:
|
||||
|
@ -285,7 +294,8 @@ class _Method(XML_Method):
|
|||
return self
|
||||
# The old method returned a new instance, but this seemed wasteful.
|
||||
# The only thing that changes is the name.
|
||||
#return _Method(self.__send, "%s.%s" % (self.__name, name))
|
||||
# return _Method(self.__send, "%s.%s" % (self.__name, name))
|
||||
|
||||
|
||||
class _Notify(object):
|
||||
def __init__(self, request):
|
||||
|
@ -293,11 +303,13 @@ class _Notify(object):
|
|||
|
||||
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 = []
|
||||
|
@ -318,14 +330,15 @@ class MultiCallMethod(object):
|
|||
|
||||
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
|
||||
|
||||
|
@ -334,8 +347,9 @@ class MultiCallNotify(object):
|
|||
self.multicall._job_list.append(new_job)
|
||||
return new_job
|
||||
|
||||
|
||||
class MultiCallIterator(object):
|
||||
|
||||
|
||||
def __init__(self, results):
|
||||
self.results = results
|
||||
|
||||
|
@ -352,8 +366,9 @@ class MultiCallIterator(object):
|
|||
def __len__(self):
|
||||
return len(self.results)
|
||||
|
||||
|
||||
class MultiCall(object):
|
||||
|
||||
|
||||
def __init__(self, server):
|
||||
self._server = server
|
||||
self._job_list = []
|
||||
|
@ -362,8 +377,8 @@ class MultiCall(object):
|
|||
if len(self._job_list) < 1:
|
||||
# Should we alert? This /is/ pretty obvious.
|
||||
return
|
||||
request_body = '[ %s ]' % ','.join([job.request() for
|
||||
job in self._job_list])
|
||||
request_body = '[ {0} ]'.format(
|
||||
','.join([job.request() for job in self._job_list]))
|
||||
responses = self._server._run_request(request_body)
|
||||
del self._job_list[:]
|
||||
if not responses:
|
||||
|
@ -381,19 +396,21 @@ class MultiCall(object):
|
|||
|
||||
__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
|
||||
|
||||
|
||||
class Fault(object):
|
||||
# JSON-RPC error class
|
||||
|
||||
def __init__(self, code=-32000, message='Server error', rpcid=None):
|
||||
self.faultCode = code
|
||||
self.faultString = message
|
||||
self.rpcid = rpcid
|
||||
|
||||
def error(self):
|
||||
return {'code':self.faultCode, 'message':self.faultString}
|
||||
return {'code': self.faultCode, 'message': self.faultString}
|
||||
|
||||
def response(self, rpcid=None, version=None):
|
||||
if not version:
|
||||
|
@ -407,25 +424,27 @@ class Fault(object):
|
|||
def __repr__(self):
|
||||
return '<Fault %s: %s>' % (self.faultCode, self.faultString)
|
||||
|
||||
|
||||
def random_id(length=8):
|
||||
return_id = ''
|
||||
for i in range(length):
|
||||
return_id += random.choice(IDCHARS)
|
||||
return return_id
|
||||
|
||||
|
||||
class Payload(dict):
|
||||
def __init__(self, rpcid=None, version=None):
|
||||
if not version:
|
||||
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 }
|
||||
request = {'id': self.id, 'method': method}
|
||||
if params:
|
||||
request['params'] = params
|
||||
if self.version >= 2:
|
||||
|
@ -441,7 +460,7 @@ class Payload(dict):
|
|||
return request
|
||||
|
||||
def response(self, result=None):
|
||||
response = {'result':result, 'id':self.id}
|
||||
response = {'result': result, 'id': self.id}
|
||||
if self.version >= 2:
|
||||
response['jsonrpc'] = str(self.version)
|
||||
else:
|
||||
|
@ -454,13 +473,15 @@ class Payload(dict):
|
|||
del error['result']
|
||||
else:
|
||||
error['result'] = None
|
||||
error['error'] = {'code':code, 'message':message}
|
||||
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:
|
||||
|
@ -469,7 +490,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.
|
||||
"""
|
||||
|
@ -482,10 +503,14 @@ def dumps(params=[], methodname=None, methodresponse=None,
|
|||
if type(params) is Fault:
|
||||
response = payload.error(params.faultCode, params.faultString)
|
||||
return jdumps(response, encoding=encoding)
|
||||
if type(methodname) not in types.StringTypes and methodresponse != True:
|
||||
raise ValueError('Method name must be a string, or methodresponse '+
|
||||
'must be set to True.')
|
||||
if config.use_jsonclass == True:
|
||||
|
||||
if type(methodname) not in types.StringTypes and \
|
||||
methodresponse is not True:
|
||||
raise ValueError(
|
||||
'Method name must be a string, or methodresponse must '
|
||||
'be set to True.')
|
||||
|
||||
if config.use_jsonclass is True:
|
||||
from jsonrpclib import jsonclass
|
||||
params = jsonclass.dump(params)
|
||||
if methodresponse is True:
|
||||
|
@ -494,12 +519,13 @@ def dumps(params=[], methodname=None, methodresponse=None,
|
|||
response = payload.response(params)
|
||||
return jdumps(response, encoding=encoding)
|
||||
request = None
|
||||
if notify == True:
|
||||
if notify is True:
|
||||
request = payload.notify(methodname, params)
|
||||
else:
|
||||
request = payload.request(methodname, params)
|
||||
return jdumps(request, encoding=encoding)
|
||||
|
||||
|
||||
def loads(data):
|
||||
"""
|
||||
This differs from the Python implementation, in that it returns
|
||||
|
@ -510,36 +536,39 @@ 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:
|
||||
if config.use_jsonclass is True:
|
||||
from jsonrpclib import jsonclass
|
||||
result = jsonclass.load(result)
|
||||
return result
|
||||
|
||||
|
||||
def check_for_errors(result):
|
||||
if not result:
|
||||
# Notification
|
||||
return result
|
||||
if type(result) is not types.DictType:
|
||||
|
||||
if not isinstance(result, dict):
|
||||
raise TypeError('Response is not a dict.')
|
||||
if 'jsonrpc' in result.keys() and float(result['jsonrpc']) > 2.0:
|
||||
raise NotImplementedError('JSON-RPC version not yet supported.')
|
||||
if 'result' not in result.keys() and 'error' not in result.keys():
|
||||
raise ValueError('Response does not have a result or error key.')
|
||||
if 'error' in result.keys() and result['error'] != None:
|
||||
if 'error' in result.keys() and result['error'] is not None:
|
||||
code = result['error']['code']
|
||||
message = result['error']['message']
|
||||
raise ProtocolError((code, message))
|
||||
return result
|
||||
|
||||
|
||||
def isbatch(result):
|
||||
if type(result) not in (types.ListType, types.TupleType):
|
||||
return False
|
||||
if len(result) < 1:
|
||||
return False
|
||||
if type(result[0]) is not types.DictType:
|
||||
if not isinstance(result[0], dict):
|
||||
return False
|
||||
if 'jsonrpc' not in result[0].keys():
|
||||
return False
|
||||
|
@ -551,11 +580,12 @@ def isbatch(result):
|
|||
return False
|
||||
return True
|
||||
|
||||
|
||||
def isnotification(request):
|
||||
if 'id' not in request.keys():
|
||||
# 2.0 notification
|
||||
return True
|
||||
if request['id'] == None:
|
||||
if request['id'] is None:
|
||||
# 1.0 notification
|
||||
return True
|
||||
return False
|
||||
|
|
279
tests.py
279
tests.py
|
@ -1,16 +1,11 @@
|
|||
"""
|
||||
The tests in this file compare the request and response objects
|
||||
to the JSON-RPC 2.0 specification document, as well as testing
|
||||
several internal components of the jsonrpclib library. Run this
|
||||
several internal components of the jsonrpclib library. Run this
|
||||
module without any parameters to run the tests.
|
||||
|
||||
Currently, this is not easily tested with a framework like
|
||||
nosetests because we spin up a daemon thread running the
|
||||
the Server, and nosetests (at least in my tests) does not
|
||||
ever "kill" the thread.
|
||||
|
||||
If you are testing jsonrpclib and the module doesn't return to
|
||||
the command prompt after running the tests, you can hit
|
||||
the command prompt after running the tests, you can hit
|
||||
"Ctrl-C" (or "Ctrl-Break" on Windows) and that should kill it.
|
||||
|
||||
TODO:
|
||||
|
@ -19,36 +14,47 @@ TODO:
|
|||
* Implement JSONClass, History, Config tests
|
||||
"""
|
||||
|
||||
from jsonrpclib import Server, MultiCall, history, config, ProtocolError
|
||||
from jsonrpclib import jsonrpc
|
||||
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
|
||||
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCRequestHandler
|
||||
import socket
|
||||
import tempfile
|
||||
import unittest
|
||||
import os
|
||||
import time
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
from threading import Thread
|
||||
|
||||
PORTS = range(8000, 8999)
|
||||
if sys.version_info < (2, 7):
|
||||
import unittest2 as unittest
|
||||
else:
|
||||
import unittest
|
||||
|
||||
from jsonrpclib import Server, MultiCall, history, ProtocolError
|
||||
from jsonrpclib import jsonrpc
|
||||
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
|
||||
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCRequestHandler
|
||||
|
||||
|
||||
def get_port(family=socket.AF_INET):
|
||||
sock = socket.socket(family, socket.SOCK_STREAM)
|
||||
sock.bind(("localhost", 0))
|
||||
return sock.getsockname()[1]
|
||||
|
||||
|
||||
class TestCompatibility(unittest.TestCase):
|
||||
|
||||
|
||||
client = None
|
||||
port = None
|
||||
server = None
|
||||
|
||||
|
||||
def setUp(self):
|
||||
self.port = PORTS.pop()
|
||||
self.port = get_port()
|
||||
self.server = server_set_up(addr=('', self.port))
|
||||
self.client = Server('http://localhost:%d' % self.port)
|
||||
|
||||
|
||||
# v1 tests forthcoming
|
||||
|
||||
|
||||
# Version 2.0 Tests
|
||||
def test_positional(self):
|
||||
""" Positional arguments in a single call """
|
||||
|
@ -59,7 +65,7 @@ class TestCompatibility(unittest.TestCase):
|
|||
request = json.loads(history.request)
|
||||
response = json.loads(history.response)
|
||||
verify_request = {
|
||||
"jsonrpc": "2.0", "method": "subtract",
|
||||
"jsonrpc": "2.0", "method": "subtract",
|
||||
"params": [42, 23], "id": request['id']
|
||||
}
|
||||
verify_response = {
|
||||
|
@ -67,7 +73,7 @@ class TestCompatibility(unittest.TestCase):
|
|||
}
|
||||
self.assertTrue(request == verify_request)
|
||||
self.assertTrue(response == verify_response)
|
||||
|
||||
|
||||
def test_named(self):
|
||||
""" Named arguments in a single call """
|
||||
result = self.client.subtract(subtrahend=23, minuend=42)
|
||||
|
@ -77,8 +83,8 @@ class TestCompatibility(unittest.TestCase):
|
|||
request = json.loads(history.request)
|
||||
response = json.loads(history.response)
|
||||
verify_request = {
|
||||
"jsonrpc": "2.0", "method": "subtract",
|
||||
"params": {"subtrahend": 23, "minuend": 42},
|
||||
"jsonrpc": "2.0", "method": "subtract",
|
||||
"params": {"subtrahend": 23, "minuend": 42},
|
||||
"id": request['id']
|
||||
}
|
||||
verify_response = {
|
||||
|
@ -86,101 +92,103 @@ class TestCompatibility(unittest.TestCase):
|
|||
}
|
||||
self.assertTrue(request == verify_request)
|
||||
self.assertTrue(response == verify_response)
|
||||
|
||||
|
||||
def test_notification(self):
|
||||
""" Testing a notification (response should be null) """
|
||||
result = self.client._notify.update(1, 2, 3, 4, 5)
|
||||
self.assertTrue(result == None)
|
||||
self.assertTrue(result is None)
|
||||
request = json.loads(history.request)
|
||||
response = history.response
|
||||
verify_request = {
|
||||
"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]
|
||||
"jsonrpc": "2.0", "method": "update", "params": [1, 2, 3, 4, 5]
|
||||
}
|
||||
verify_response = ''
|
||||
self.assertTrue(request == verify_request)
|
||||
self.assertTrue(response == verify_response)
|
||||
|
||||
|
||||
def test_non_existent_method(self):
|
||||
self.assertRaises(ProtocolError, self.client.foobar)
|
||||
with self.assertRaises(ProtocolError):
|
||||
self.client.foobar()
|
||||
|
||||
request = json.loads(history.request)
|
||||
response = json.loads(history.response)
|
||||
verify_request = {
|
||||
"jsonrpc": "2.0", "method": "foobar", "id": request['id']
|
||||
}
|
||||
verify_response = {
|
||||
"jsonrpc": "2.0",
|
||||
"error":
|
||||
{"code": -32601, "message": response['error']['message']},
|
||||
"jsonrpc": "2.0",
|
||||
"error":
|
||||
{"code": -32601, "message": response['error']['message']},
|
||||
"id": request['id']
|
||||
}
|
||||
self.assertTrue(request == verify_request)
|
||||
self.assertTrue(response == verify_response)
|
||||
|
||||
|
||||
def test_invalid_json(self):
|
||||
invalid_json = '{"jsonrpc": "2.0", "method": "foobar, '+ \
|
||||
invalid_json = '{"jsonrpc": "2.0", "method": "foobar, ' + \
|
||||
'"params": "bar", "baz]'
|
||||
response = self.client._run_request(invalid_json)
|
||||
response = json.loads(history.response)
|
||||
verify_response = json.loads(
|
||||
'{"jsonrpc": "2.0", "error": {"code": -32700,'+
|
||||
'{"jsonrpc": "2.0", "error": {"code": -32700,' +
|
||||
' "message": "Parse error."}, "id": null}'
|
||||
)
|
||||
verify_response['error']['message'] = response['error']['message']
|
||||
self.assertTrue(response == verify_response)
|
||||
|
||||
|
||||
def test_invalid_request(self):
|
||||
invalid_request = '{"jsonrpc": "2.0", "method": 1, "params": "bar"}'
|
||||
response = self.client._run_request(invalid_request)
|
||||
response = json.loads(history.response)
|
||||
verify_response = json.loads(
|
||||
'{"jsonrpc": "2.0", "error": {"code": -32600, '+
|
||||
'{"jsonrpc": "2.0", "error": {"code": -32600, ' +
|
||||
'"message": "Invalid Request."}, "id": null}'
|
||||
)
|
||||
verify_response['error']['message'] = response['error']['message']
|
||||
self.assertTrue(response == verify_response)
|
||||
|
||||
|
||||
def test_batch_invalid_json(self):
|
||||
invalid_request = '[ {"jsonrpc": "2.0", "method": "sum", '+ \
|
||||
'"params": [1,2,4], "id": "1"},{"jsonrpc": "2.0", "method" ]'
|
||||
invalid_request = '[ {"jsonrpc": "2.0", "method": "sum", ' + \
|
||||
'"params": [1, 2, 4], "id": "1"},{"jsonrpc": "2.0", "method" ]'
|
||||
response = self.client._run_request(invalid_request)
|
||||
response = json.loads(history.response)
|
||||
verify_response = json.loads(
|
||||
'{"jsonrpc": "2.0", "error": {"code": -32700,'+
|
||||
'{"jsonrpc": "2.0", "error": {"code": -32700,' +
|
||||
'"message": "Parse error."}, "id": null}'
|
||||
)
|
||||
verify_response['error']['message'] = response['error']['message']
|
||||
self.assertTrue(response == verify_response)
|
||||
|
||||
|
||||
def test_empty_array(self):
|
||||
invalid_request = '[]'
|
||||
response = self.client._run_request(invalid_request)
|
||||
response = json.loads(history.response)
|
||||
verify_response = json.loads(
|
||||
'{"jsonrpc": "2.0", "error": {"code": -32600, '+
|
||||
'{"jsonrpc": "2.0", "error": {"code": -32600, ' +
|
||||
'"message": "Invalid Request."}, "id": null}'
|
||||
)
|
||||
verify_response['error']['message'] = response['error']['message']
|
||||
self.assertTrue(response == verify_response)
|
||||
|
||||
|
||||
def test_nonempty_array(self):
|
||||
invalid_request = '[1,2]'
|
||||
invalid_request = '[1, 2]'
|
||||
request_obj = json.loads(invalid_request)
|
||||
response = self.client._run_request(invalid_request)
|
||||
response = json.loads(history.response)
|
||||
self.assertTrue(len(response) == len(request_obj))
|
||||
for resp in response:
|
||||
verify_resp = json.loads(
|
||||
'{"jsonrpc": "2.0", "error": {"code": -32600, '+
|
||||
'{"jsonrpc": "2.0", "error": {"code": -32600, ' +
|
||||
'"message": "Invalid Request."}, "id": null}'
|
||||
)
|
||||
verify_resp['error']['message'] = resp['error']['message']
|
||||
self.assertTrue(resp == verify_resp)
|
||||
|
||||
|
||||
def test_batch(self):
|
||||
multicall = MultiCall(self.client)
|
||||
multicall.sum(1,2,4)
|
||||
multicall.sum(1, 2, 4)
|
||||
multicall._notify.notify_hello(7)
|
||||
multicall.subtract(42,23)
|
||||
multicall.subtract(42, 23)
|
||||
multicall.foo.get(name='myself')
|
||||
multicall.get_data()
|
||||
job_requests = [j.request() for j in multicall._job_list]
|
||||
|
@ -188,38 +196,44 @@ class TestCompatibility(unittest.TestCase):
|
|||
json_requests = '[%s]' % ','.join(job_requests)
|
||||
requests = json.loads(json_requests)
|
||||
responses = self.client._run_request(json_requests)
|
||||
|
||||
|
||||
verify_requests = json.loads("""[
|
||||
{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
|
||||
{"jsonrpc": "2.0", "method": "sum",
|
||||
"params": [1, 2, 4], "id": "1"},
|
||||
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
|
||||
{"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},
|
||||
{"foo": "boo"},
|
||||
{"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"},
|
||||
{"jsonrpc": "2.0", "method": "get_data", "id": "9"}
|
||||
{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23],
|
||||
"id": "2"}, {"foo": "boo"},
|
||||
{"jsonrpc": "2.0", "method": "foo.get",
|
||||
"params": {"name": "myself"}, "id": "5"},
|
||||
{"jsonrpc": "2.0", "method": "get_data", "id": "9"}
|
||||
]""")
|
||||
|
||||
|
||||
# Thankfully, these are in order so testing is pretty simple.
|
||||
verify_responses = json.loads("""[
|
||||
{"jsonrpc": "2.0", "result": 7, "id": "1"},
|
||||
{"jsonrpc": "2.0", "result": 19, "id": "2"},
|
||||
{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null},
|
||||
{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found."}, "id": "5"},
|
||||
{"jsonrpc": "2.0",
|
||||
"error": {"code": -32600, "message": "Invalid Request."},
|
||||
"id": null},
|
||||
{"jsonrpc": "2.0",
|
||||
"error": {"code": -32601, "message": "Method not found."},
|
||||
"id": "5"},
|
||||
{"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"}
|
||||
]""")
|
||||
|
||||
|
||||
self.assertTrue(len(requests) == len(verify_requests))
|
||||
self.assertTrue(len(responses) == len(verify_responses))
|
||||
|
||||
|
||||
responses_by_id = {}
|
||||
response_i = 0
|
||||
|
||||
|
||||
for i in range(len(requests)):
|
||||
verify_request = verify_requests[i]
|
||||
request = requests[i]
|
||||
response = None
|
||||
if request.get('method') != 'notify_hello':
|
||||
req_id = request.get('id')
|
||||
if verify_request.has_key('id'):
|
||||
if "id" in verify_request:
|
||||
verify_request['id'] = req_id
|
||||
verify_response = verify_responses[response_i]
|
||||
verify_response['id'] = req_id
|
||||
|
@ -227,23 +241,23 @@ class TestCompatibility(unittest.TestCase):
|
|||
response_i += 1
|
||||
response = verify_response
|
||||
self.assertTrue(request == verify_request)
|
||||
|
||||
|
||||
for response in responses:
|
||||
verify_response = responses_by_id.get(response.get('id'))
|
||||
if verify_response.has_key('error'):
|
||||
if "error" in verify_response:
|
||||
verify_response['error']['message'] = \
|
||||
response['error']['message']
|
||||
self.assertTrue(response == verify_response)
|
||||
|
||||
def test_batch_notifications(self):
|
||||
|
||||
def test_batch_notifications(self):
|
||||
multicall = MultiCall(self.client)
|
||||
multicall._notify.notify_sum(1, 2, 4)
|
||||
multicall._notify.notify_hello(7)
|
||||
result = multicall()
|
||||
self.assertTrue(len(result) == 0)
|
||||
valid_request = json.loads(
|
||||
'[{"jsonrpc": "2.0", "method": "notify_sum", '+
|
||||
'"params": [1,2,4]},{"jsonrpc": "2.0", '+
|
||||
'[{"jsonrpc": "2.0", "method": "notify_sum", ' +
|
||||
'"params": [1, 2, 4]},{"jsonrpc": "2.0", ' +
|
||||
'"method": "notify_hello", "params": [7]}]'
|
||||
)
|
||||
request = json.loads(history.request)
|
||||
|
@ -253,23 +267,24 @@ class TestCompatibility(unittest.TestCase):
|
|||
valid_req = valid_request[i]
|
||||
self.assertTrue(req == valid_req)
|
||||
self.assertTrue(history.response == '')
|
||||
|
||||
|
||||
|
||||
class InternalTests(unittest.TestCase):
|
||||
"""
|
||||
These tests verify that the client and server portions of
|
||||
"""
|
||||
These tests verify that the client and server portions of
|
||||
jsonrpclib talk to each other properly.
|
||||
"""
|
||||
"""
|
||||
client = None
|
||||
server = None
|
||||
port = None
|
||||
|
||||
|
||||
def setUp(self):
|
||||
self.port = PORTS.pop()
|
||||
self.port = get_port()
|
||||
self.server = server_set_up(addr=('', self.port))
|
||||
|
||||
|
||||
def get_client(self):
|
||||
return Server('http://localhost:%d' % self.port)
|
||||
|
||||
|
||||
def get_multicall_client(self):
|
||||
server = self.get_client()
|
||||
return MultiCall(server)
|
||||
|
@ -278,33 +293,34 @@ class InternalTests(unittest.TestCase):
|
|||
client = self.get_client()
|
||||
result = client.ping()
|
||||
self.assertTrue(result)
|
||||
|
||||
|
||||
def test_single_args(self):
|
||||
client = self.get_client()
|
||||
result = client.add(5, 10)
|
||||
self.assertTrue(result == 15)
|
||||
|
||||
|
||||
def test_single_kwargs(self):
|
||||
client = self.get_client()
|
||||
result = client.add(x=5, y=10)
|
||||
self.assertTrue(result == 15)
|
||||
|
||||
|
||||
def test_single_kwargs_and_args(self):
|
||||
client = self.get_client()
|
||||
self.assertRaises(ProtocolError, client.add, (5,), {'y':10})
|
||||
|
||||
with self.assertRaises(ProtocolError):
|
||||
client.add(5, y=10)
|
||||
|
||||
def test_single_notify(self):
|
||||
client = self.get_client()
|
||||
result = client._notify.add(5, 10)
|
||||
self.assertTrue(result == None)
|
||||
|
||||
self.assertTrue(result is None)
|
||||
|
||||
def test_single_namespace(self):
|
||||
client = self.get_client()
|
||||
response = client.namespace.sum(1,2,4)
|
||||
response = client.namespace.sum(1, 2, 4)
|
||||
request = json.loads(history.request)
|
||||
response = json.loads(history.response)
|
||||
verify_request = {
|
||||
"jsonrpc": "2.0", "params": [1, 2, 4],
|
||||
"jsonrpc": "2.0", "params": [1, 2, 4],
|
||||
"id": "5", "method": "namespace.sum"
|
||||
}
|
||||
verify_response = {
|
||||
|
@ -314,25 +330,18 @@ class InternalTests(unittest.TestCase):
|
|||
verify_response['id'] = request['id']
|
||||
self.assertTrue(verify_request == request)
|
||||
self.assertTrue(verify_response == response)
|
||||
|
||||
|
||||
def test_multicall_success(self):
|
||||
multicall = self.get_multicall_client()
|
||||
multicall.ping()
|
||||
multicall.add(5, 10)
|
||||
multicall.namespace.sum([5, 10, 15])
|
||||
multicall.namespace.sum(5, 10, 15)
|
||||
correct = [True, 15, 30]
|
||||
i = 0
|
||||
for result in multicall():
|
||||
self.assertTrue(result == correct[i])
|
||||
i += 1
|
||||
|
||||
def test_multicall_success(self):
|
||||
multicall = self.get_multicall_client()
|
||||
for i in range(3):
|
||||
multicall.add(5, i)
|
||||
result = multicall()
|
||||
self.assertTrue(result[2] == 7)
|
||||
|
||||
|
||||
def test_multicall_failure(self):
|
||||
multicall = self.get_multicall_client()
|
||||
multicall.ping()
|
||||
|
@ -345,87 +354,96 @@ class InternalTests(unittest.TestCase):
|
|||
else:
|
||||
def func():
|
||||
return result[i]
|
||||
self.assertRaises(raises[i], func)
|
||||
|
||||
|
||||
with self.assertRaises(raises[i]):
|
||||
func()
|
||||
|
||||
|
||||
if jsonrpc.USE_UNIX_SOCKETS:
|
||||
# We won't do these tests unless Unix Sockets are supported
|
||||
|
||||
|
||||
@unittest.skip("Skipping Unix socket tests right now.")
|
||||
class UnixSocketInternalTests(InternalTests):
|
||||
"""
|
||||
These tests run the same internal communication tests,
|
||||
These tests run the same internal communication tests,
|
||||
but over a Unix socket instead of a TCP socket.
|
||||
"""
|
||||
def setUp(self):
|
||||
suffix = "%d.sock" % PORTS.pop()
|
||||
|
||||
# Open to safer, alternative processes
|
||||
suffix = "%d.sock" % get_port()
|
||||
|
||||
# Open to safer, alternative processes
|
||||
# for getting a temp file name...
|
||||
temp = tempfile.NamedTemporaryFile(
|
||||
suffix=suffix
|
||||
)
|
||||
self.port = temp.name
|
||||
temp.close()
|
||||
|
||||
|
||||
self.server = server_set_up(
|
||||
addr=self.port,
|
||||
addr=self.port,
|
||||
address_family=socket.AF_UNIX
|
||||
)
|
||||
|
||||
def get_client(self):
|
||||
return Server('unix:/%s' % self.port)
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
""" Removes the tempory socket file """
|
||||
os.unlink(self.port)
|
||||
|
||||
|
||||
|
||||
class UnixSocketErrorTests(unittest.TestCase):
|
||||
"""
|
||||
Simply tests that the proper exceptions fire if
|
||||
"""
|
||||
Simply tests that the proper exceptions fire if
|
||||
Unix sockets are attempted to be used on a platform
|
||||
that doesn't support them.
|
||||
"""
|
||||
|
||||
|
||||
def setUp(self):
|
||||
self.original_value = jsonrpc.USE_UNIX_SOCKETS
|
||||
if (jsonrpc.USE_UNIX_SOCKETS):
|
||||
jsonrpc.USE_UNIX_SOCKETS = False
|
||||
|
||||
|
||||
def test_client(self):
|
||||
address = "unix://shouldnt/work.sock"
|
||||
self.assertRaises(
|
||||
jsonrpc.UnixSocketMissing,
|
||||
Server,
|
||||
address
|
||||
)
|
||||
|
||||
with self.assertRaises(jsonrpc.UnixSocketMissing):
|
||||
Server(address)
|
||||
|
||||
def tearDown(self):
|
||||
jsonrpc.USE_UNIX_SOCKETS = self.original_value
|
||||
|
||||
|
||||
|
||||
""" Test Methods """
|
||||
|
||||
|
||||
def subtract(minuend, subtrahend):
|
||||
""" Using the keywords from the JSON-RPC v2 doc """
|
||||
return minuend-subtrahend
|
||||
|
||||
|
||||
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
|
||||
|
||||
def update(*args):
|
||||
return args
|
||||
|
||||
|
||||
|
||||
def summation(*args):
|
||||
return sum(args)
|
||||
|
||||
|
||||
|
||||
def notify_hello(*args):
|
||||
return args
|
||||
|
||||
|
||||
|
||||
def get_data():
|
||||
return ['hello', 5]
|
||||
|
||||
|
||||
|
||||
def ping():
|
||||
return True
|
||||
|
||||
|
||||
|
||||
def server_set_up(addr, address_family=socket.AF_INET):
|
||||
# Not sure this is a good idea to spin up a new server thread
|
||||
# for each test... but it seems to work fine.
|
||||
|
@ -447,10 +465,3 @@ def server_set_up(addr, address_family=socket.AF_INET):
|
|||
server_proc.daemon = True
|
||||
server_proc.start()
|
||||
return server_proc
|
||||
|
||||
if __name__ == '__main__':
|
||||
print "==============================================================="
|
||||
print " NOTE: There may be threading exceptions after tests finish. "
|
||||
print "==============================================================="
|
||||
time.sleep(2)
|
||||
unittest.main()
|
||||
|
|
Loading…
Reference in New Issue