summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.circleci/config.yml51
-rw-r--r--.travis.yml17
-rw-r--r--README.md3
-rw-r--r--dev-requirements.txt18
-rw-r--r--jsonrpclib/SimpleJSONRPCServer.py77
-rw-r--r--jsonrpclib/__init__.py10
-rw-r--r--jsonrpclib/history.py9
-rw-r--r--jsonrpclib/jsonclass.py46
-rw-r--r--jsonrpclib/jsonrpc.py117
-rw-r--r--pyproject.toml6
-rw-r--r--setup.cfg13
-rwxr-xr-xsetup.py35
-rw-r--r--tests.py141
-rw-r--r--tox.ini9
14 files changed, 316 insertions, 236 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..4c499eb
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,51 @@
+version: 2.1
+
+jobs:
+ build-and-test:
+ docker:
+ - image: cimg/python:3.9.2
+ steps:
+ - checkout
+ - restore_cache:
+ keys:
+ - dev-cache-{{ checksum "dev-requirements.txt" }}-{{ checksum "tox.ini" }}
+ - run:
+ name: Install dev dependencies
+ command: pip3 install -r dev-requirements.txt
+ - run: pyenv install --skip-existing 3.5-dev
+ - run: pyenv install --skip-existing 3.6-dev
+ - run: pyenv install --skip-existing 3.7-dev
+ - run: pyenv install --skip-existing 3.8-dev
+ - run: pyenv install --skip-existing 3.9-dev
+ - run:
+ name: Set local Python versions
+ command: pyenv local 3.5-dev 3.6-dev 3.7-dev 3.8-dev 3.9-dev 3.9.2
+ - save_cache:
+ key: dev-cache-{{ checksum "dev-requirements.txt" }}-{{ checksum "tox.ini" }}
+ paths:
+ - /home/circleci/.cache/pip
+ - /home/circleci/.pyenv/versions/
+ - /home/circleci/.local/lib/
+
+ - run:
+ command: flake8
+ name: Flake8 / linting
+ - run:
+ command: tox
+ name: Run package tests across all python variants
+ - store_test_results:
+ path: reports
+ - run:
+ command: pip install build
+ name: Install build dependency
+ - run:
+ command: python3 -m build
+ name: Build source distribution
+ - store_artifacts:
+ path: ./dist
+
+workflows:
+ main:
+ jobs:
+ - build-and-test
+
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index fe1cd05..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-language: python
-sudo: false
-python:
-- '2.7'
-install:
-- pip install -r dev-requirements.txt
-- pip install twine
-script: tox
-deploy:
- provider: pypi
- user: joshmarshall
- password:
- secure: RbRcj7YdDXE9U2/a9yg4DW9UXFfrGWrM+S8uE8RgYu1D9njDDzUyNcFygaBXd8rxd8GpwRXHzSAO31B/Rk4NVbbM7JtcIA/52jx5j+4DdmEhffnzvahDkCZT6EV5iS3IxlShbuxgbzp3Qz14jF7Kl9iBSCOlIFItLCDlK7rfyJU=
- on:
- tags: true
- repo: joshmarshall/jsonrpclib
- branch: master
diff --git a/README.md b/README.md
index 9513c03..a5a0e9e 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
-[![Build Status](https://travis-ci.org/joshmarshall/jsonrpclib.svg)](https://travis-ci.org/joshmarshall/jsonrpclib)
+[![Build Status](https://circleci.com/gh/joshmarshall/jsonrpclib.svg?style=svg)](https://circleci.com/gh/joshmarshall/jsonrpclib)
+
JSONRPClib
==========
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 95cb849..c85472b 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,11 +1,7 @@
-coverage==4.0
-linecache2==1.0.0
-nose==1.3.7
-pluggy==0.3.1
-py==1.4.30
-six==1.9.0
-tox==2.1.1
-traceback2==1.4.0
-unittest2==1.1.0
-virtualenv==13.1.2
-wheel==0.24.0
+build
+coverage
+flake8
+pytest
+tox
+tox-pyenv
+twine
diff --git a/jsonrpclib/SimpleJSONRPCServer.py b/jsonrpclib/SimpleJSONRPCServer.py
index a22ae7c..e6a46f3 100644
--- a/jsonrpclib/SimpleJSONRPCServer.py
+++ b/jsonrpclib/SimpleJSONRPCServer.py
@@ -1,20 +1,21 @@
-import jsonrpclib
-from jsonrpclib import Fault
-from jsonrpclib.jsonrpc import USE_UNIX_SOCKETS
-import SimpleXMLRPCServer
-import SocketServer
-import socket
import logging
import os
-import types
-import traceback
+import socket
+import socketserver
import sys
+import traceback
+import xmlrpc.server
+
try:
import fcntl
except ImportError:
# For Windows
fcntl = None
+import jsonrpclib
+from jsonrpclib import Fault
+from jsonrpclib.jsonrpc import USE_UNIX_SOCKETS
+
def get_version(request):
# must be a dict
@@ -39,9 +40,8 @@ def validate_request(request):
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:
+ if not method or not isinstance(method, str) or \
+ not isinstance(params, (list, dict, tuple)):
fault = Fault(
-32600, 'Invalid request parameters or method.', rpcid=rpcid
)
@@ -49,17 +49,17 @@ def validate_request(request):
return True
-class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
+class SimpleJSONRPCDispatcher(xmlrpc.server.SimpleXMLRPCDispatcher):
def __init__(self, encoding=None):
- SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(
+ xmlrpc.server.SimpleXMLRPCDispatcher.__init__(
self, allow_none=True, encoding=encoding)
def _marshaled_dispatch(self, data, dispatch_method=None):
response = None
try:
request = jsonrpclib.loads(data)
- except Exception, e:
+ except Exception as e:
fault = Fault(-32700, 'Request %s invalid. (%s)' % (data, e))
response = fault.response()
return response
@@ -97,7 +97,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
params = request.get('params')
try:
response = self._dispatch(method, params)
- except:
+ except Exception:
exc_type, exc_value, exc_tb = sys.exc_info()
fault = Fault(-32603, '%s:%s' % (exc_type, exc_value))
return fault.response()
@@ -111,7 +111,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
rpcid=request['id']
)
return response
- except:
+ except Exception:
exc_type, exc_value, exc_tb = sys.exc_info()
fault = Fault(-32603, '%s:%s' % (exc_type, exc_value))
return fault.response()
@@ -126,7 +126,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
return self.instance._dispatch(method, params)
else:
try:
- func = SimpleXMLRPCServer.resolve_dotted_attribute(
+ func = xmlrpc.server.resolve_dotted_attribute(
self.instance,
method,
True
@@ -135,14 +135,14 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
pass
if func is not None:
try:
- if isinstance(params, types.ListType):
+ if isinstance(params, list):
response = func(*params)
else:
response = func(**params)
return response
# except TypeError:
# return Fault(-32602, 'Invalid parameters.')
- except:
+ except Exception:
err_lines = traceback.format_exc().splitlines()
trace_string = '%s | %s' % (err_lines[-3], err_lines[-1])
fault = jsonrpclib.Fault(-32603, 'Server error: %s' %
@@ -153,7 +153,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
class SimpleJSONRPCRequestHandler(
- SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
+ xmlrpc.server.SimpleXMLRPCRequestHandler):
def do_POST(self):
if not self.is_rpc_path_valid():
@@ -165,7 +165,8 @@ class SimpleJSONRPCRequestHandler(
L = []
while size_remaining:
chunk_size = min(size_remaining, max_chunk_size)
- L.append(self.rfile.read(chunk_size))
+ chunk = self.rfile.read(chunk_size).decode()
+ L.append(chunk)
size_remaining -= len(L[-1])
data = ''.join(L)
response = self.server._marshaled_dispatch(data)
@@ -181,12 +182,20 @@ class SimpleJSONRPCRequestHandler(
self.send_header("Content-type", "application/json-rpc")
self.send_header("Content-length", str(len(response)))
self.end_headers()
- self.wfile.write(response)
+ if isinstance(response, bytes):
+ self.wfile.write(response)
+ else:
+ self.wfile.write(response.encode())
self.wfile.flush()
self.connection.shutdown(1)
-class SimpleJSONRPCServer(SocketServer.TCPServer, SimpleJSONRPCDispatcher):
+class SimpleJSONRPCUnixRequestHandler(SimpleJSONRPCRequestHandler):
+
+ disable_nagle_algorithm = False
+
+
+class SimpleJSONRPCServer(socketserver.TCPServer, SimpleJSONRPCDispatcher):
allow_reuse_address = True
@@ -195,10 +204,8 @@ class SimpleJSONRPCServer(SocketServer.TCPServer, SimpleJSONRPCDispatcher):
address_family=socket.AF_INET):
self.logRequests = logRequests
SimpleJSONRPCDispatcher.__init__(self, encoding)
- # TCPServer.__init__ has an extra parameter on 2.6+, so
- # check Python version and decide on how to call it
- vi = sys.version_info
self.address_family = address_family
+
if USE_UNIX_SOCKETS and address_family == socket.AF_UNIX:
# Unix sockets can't be bound if they already exist in the
# filesystem. The convention of e.g. X11 is to unlink
@@ -208,12 +215,12 @@ class SimpleJSONRPCServer(SocketServer.TCPServer, SimpleJSONRPCDispatcher):
os.unlink(addr)
except OSError:
logging.warning("Could not unlink socket %s", addr)
- # if python 2.5 and lower
- if vi[0] < 3 and vi[1] < 6:
- SocketServer.TCPServer.__init__(self, addr, requestHandler)
- else:
- SocketServer.TCPServer.__init__(
- self, addr, requestHandler, bind_and_activate)
+
+ if requestHandler == SimpleJSONRPCRequestHandler:
+ requestHandler = SimpleJSONRPCUnixRequestHandler
+
+ 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
@@ -227,9 +234,9 @@ class CGIJSONRPCRequestHandler(SimpleJSONRPCDispatcher):
def handle_jsonrpc(self, request_text):
response = self._marshaled_dispatch(request_text)
- print 'Content-Type: application/json-rpc'
- print 'Content-Length: %d' % len(response)
- print
+ print('Content-Type: application/json-rpc')
+ print('Content-Length: %d' % len(response))
+ print()
sys.stdout.write(response)
handle_xmlrpc = handle_jsonrpc
diff --git a/jsonrpclib/__init__.py b/jsonrpclib/__init__.py
index 6e884b8..91dd5f9 100644
--- a/jsonrpclib/__init__.py
+++ b/jsonrpclib/__init__.py
@@ -1,6 +1,12 @@
from jsonrpclib.config import Config
-config = Config.instance()
from jsonrpclib.history import History
-history = History.instance()
from jsonrpclib.jsonrpc import Server, MultiCall, Fault
from jsonrpclib.jsonrpc import ProtocolError, loads, dumps
+
+config = Config.instance()
+history = History.instance()
+
+__all__ = [
+ "config", "Config", "history", "History", "Server", "MultiCall",
+ "Fault", "ProtocolError", "loads", "dumps"
+]
diff --git a/jsonrpclib/history.py b/jsonrpclib/history.py
index f052baa..090f996 100644
--- a/jsonrpclib/history.py
+++ b/jsonrpclib/history.py
@@ -5,6 +5,7 @@ class History(object):
each request cycle in order to keep it from clogging
memory.
"""
+ size = 20
requests = []
responses = []
_instance = None
@@ -16,10 +17,18 @@ class History(object):
return cls._instance
def add_response(self, response_obj):
+ if self.size == 0:
+ return
self.responses.append(response_obj)
+ if self.size > 0:
+ self.responses = self.responses[0 - self.size:]
def add_request(self, request_obj):
+ if self.size == 0:
+ return
self.requests.append(request_obj)
+ if self.size > 0:
+ self.requests = self.requests[0 - self.size:]
@property
def request(self):
diff --git a/jsonrpclib/jsonclass.py b/jsonrpclib/jsonclass.py
index 4326f28..17e3653 100644
--- a/jsonrpclib/jsonclass.py
+++ b/jsonrpclib/jsonclass.py
@@ -1,30 +1,12 @@
-import types
import inspect
import re
from jsonrpclib import config
-iter_types = [
- types.DictType,
- types.ListType,
- types.TupleType
-]
-
-string_types = [
- types.StringType,
- types.UnicodeType
-]
-
-numeric_types = [
- types.IntType,
- types.LongType,
- types.FloatType
-]
-
-value_types = [
- types.BooleanType,
- types.NoneType
-]
+iter_types = (dict, list, tuple)
+value_types = (bool, )
+string_types = (str, )
+numeric_types = (int, float)
supported_types = iter_types+string_types+numeric_types+value_types
invalid_module_chars = r'[^a-zA-Z0-9\_\.]'
@@ -39,23 +21,22 @@ def dump(obj, serialize_method=None, ignore_attribute=None, ignore=[]):
serialize_method = config.serialize_method
if not ignore_attribute:
ignore_attribute = config.ignore_attribute
- obj_type = type(obj)
# Parse / return default "types"...
- if obj_type in numeric_types+string_types+value_types:
+ if obj is None or isinstance(obj, numeric_types+string_types+value_types):
return obj
- if obj_type in iter_types:
- if obj_type in (types.ListType, types.TupleType):
+ if isinstance(obj, iter_types):
+ if isinstance(obj, (list, tuple)):
new_obj = []
for item in obj:
new_obj.append(
dump(item, serialize_method, ignore_attribute, ignore))
- if isinstance(obj_type, types.TupleType):
+ if isinstance(obj, tuple):
new_obj = tuple(new_obj)
return new_obj
# It's a dict...
else:
new_obj = {}
- for key, value in obj.iteritems():
+ for key, value in obj.items():
new_obj[key] = dump(
value, serialize_method, ignore_attribute, ignore)
return new_obj
@@ -81,7 +62,7 @@ def dump(obj, serialize_method=None, ignore_attribute=None, ignore=[]):
return_obj['__jsonclass__'].append([])
attrs = {}
ignore_list = getattr(obj, ignore_attribute, [])+ignore
- for attr_name, attr_value in obj.__dict__.iteritems():
+ for attr_name, attr_value in obj.__dict__.items():
if type(attr_value) in supported_types and \
attr_name not in ignore_list and \
attr_value not in ignore_list:
@@ -92,7 +73,8 @@ def dump(obj, serialize_method=None, ignore_attribute=None, ignore=[]):
def load(obj):
- if type(obj) in string_types + numeric_types + value_types:
+ if obj is None or isinstance(
+ obj, string_types + numeric_types + value_types):
return obj
if isinstance(obj, list):
@@ -103,7 +85,7 @@ def load(obj):
# Othewise, it's a dict type
if '__jsonclass__' not in obj:
return_dict = {}
- for key, value in obj.iteritems():
+ for key, value in obj.items():
new_value = load(value)
return_dict[key] = new_value
return return_dict
@@ -148,7 +130,7 @@ def load(obj):
new_obj = json_class(**params)
else:
raise TranslationError('Constructor args must be a dict or list.')
- for key, value in obj.iteritems():
+ for key, value in obj.items():
if key == '__jsonclass__':
continue
setattr(new_obj, key, value)
diff --git a/jsonrpclib/jsonrpc.py b/jsonrpclib/jsonrpc.py
index 167bcd7..9eeadcb 100644
--- a/jsonrpclib/jsonrpc.py
+++ b/jsonrpclib/jsonrpc.py
@@ -46,34 +46,28 @@ appropriately.
See http://code.google.com/p/jsonrpclib/ for more info.
"""
-import types
-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
+from xmlrpc.client import Transport as XMLTransport
+from xmlrpc.client import SafeTransport as XMLSafeTransport
+from xmlrpc.client import ServerProxy as XMLServerProxy
+from xmlrpc.client import _Method as XML_Method
+import json
import string
import random
-# Library includes
-from jsonrpclib import config
-from jsonrpclib import history
+from jsonrpclib import Config
+from jsonrpclib import History
+
+from http.client import HTTPConnection
+from socket import socket
+
+USE_UNIX_SOCKETS = False
-# JSON library importing
-cjson = None
-json = None
try:
- import cjson
+ from socket import AF_UNIX, SOCK_STREAM
+ USE_UNIX_SOCKETS = True
except ImportError:
- try:
- import json
- except ImportError:
- try:
- import simplejson as json
- except ImportError:
- raise ImportError(
- 'You must have the cjson, json, or simplejson ' +
- 'module(s) available.'
- )
+ pass
+
IDCHARS = string.ascii_lowercase+string.digits
@@ -91,19 +85,11 @@ class UnixSocketMissing(Exception):
def jdumps(obj, encoding='utf-8'):
# Do 'serialize' test at some point for other classes
- global cjson
- if cjson:
- return cjson.encode(obj)
- else:
- return json.dumps(obj, encoding=encoding)
+ return json.dumps(obj)
def jloads(json_string):
- global cjson
- if cjson:
- return cjson.decode(json_string)
- else:
- return json.loads(json_string)
+ return json.loads(json_string)
# XMLRPClib re-implementations
@@ -115,16 +101,15 @@ class ProtocolError(Exception):
class TransportMixIn(object):
""" Just extends the XMLRPC transport where necessary. """
- user_agent = config.user_agent
- # for Python 2.7 support
- _connection = (None, None)
- _extra_headers = []
+ user_agent = Config.instance().user_agent
def send_content(self, connection, request_body):
connection.putheader("Content-Type", "application/json-rpc")
connection.putheader("Content-Length", str(len(request_body)))
connection.endheaders()
if request_body:
+ if type(request_body) == str:
+ request_body = request_body.encode("utf8")
connection.send(request_body)
def getparser(self):
@@ -151,7 +136,7 @@ class JSONTarget(object):
self.data.append(data)
def close(self):
- return ''.join(self.data)
+ return ''.join([d.decode() for d in self.data])
class Transport(TransportMixIn, XMLTransport):
@@ -165,16 +150,6 @@ class SafeTransport(TransportMixIn, XMLSafeTransport):
TransportMixIn.__init__(self)
XMLSafeTransport.__init__(self)
-from httplib import HTTP, HTTPConnection
-from socket import socket
-
-USE_UNIX_SOCKETS = False
-
-try:
- from socket import AF_UNIX, SOCK_STREAM
- USE_UNIX_SOCKETS = True
-except ImportError:
- pass
if (USE_UNIX_SOCKETS):
@@ -183,14 +158,11 @@ if (USE_UNIX_SOCKETS):
self.sock = socket(AF_UNIX, SOCK_STREAM)
self.sock.connect(self.host)
- class UnixHTTP(HTTP):
- _connection_class = UnixHTTPConnection
-
class UnixTransport(TransportMixIn, XMLTransport):
def make_connection(self, host):
host, extra_headers, x509 = self.get_host_info(host)
- return UnixHTTP(host)
+ return UnixHTTPConnection(host)
class ServerProxy(XMLServerProxy):
@@ -201,11 +173,14 @@ class ServerProxy(XMLServerProxy):
def __init__(self, uri, transport=None, encoding=None,
verbose=0, version=None):
- import urllib
+ try:
+ from urllib.parse import splittype, splithost # python 3.x
+ except ImportError:
+ from urllib.parse import splittype, splithost
if not version:
- version = config.version
+ version = Config.instance().version
self.__version = version
- schema, uri = urllib.splittype(uri)
+ schema, uri = splittype(uri)
if schema not in ('http', 'https', 'unix'):
raise IOError('Unsupported JSON-RPC protocol.')
if schema == 'unix':
@@ -215,7 +190,7 @@ class ServerProxy(XMLServerProxy):
self.__host = uri
self.__handler = '/'
else:
- self.__host, self.__handler = urllib.splithost(uri)
+ self.__host, self.__handler = splithost(uri)
if not self.__handler:
# Not sure if this is in the JSON spec?
# self.__handler = '/'
@@ -246,7 +221,7 @@ class ServerProxy(XMLServerProxy):
return
def _run_request(self, request, notify=None):
- history.add_request(request)
+ History.instance().add_request(request)
response = self.__transport.request(
self.__host,
@@ -261,7 +236,7 @@ class ServerProxy(XMLServerProxy):
# the response object, or expect the Server to be
# outputting the response appropriately?
- history.add_response(response)
+ History.instance().add_response(response)
if not response:
return None
return_obj = loads(response)
@@ -361,7 +336,6 @@ class MultiCallIterator(object):
def __iter__(self):
for i in range(0, len(self.results)):
yield self[i]
- raise StopIteration
def __getitem__(self, i):
item = self.results[i]
@@ -401,6 +375,7 @@ class MultiCall(object):
__call__ = _request
+
# These lines conform to xmlrpclib's "compatibility" line.
# Not really sure if we should include these, but oh well.
Server = ServerProxy
@@ -419,7 +394,7 @@ class Fault(object):
def response(self, rpcid=None, version=None):
if not version:
- version = config.version
+ version = Config.instance().version
if rpcid:
self.rpcid = rpcid
return dumps(
@@ -440,12 +415,12 @@ def random_id(length=8):
class Payload(dict):
def __init__(self, rpcid=None, version=None):
if not version:
- version = config.version
+ version = Config.instance().version
self.id = rpcid
self.version = float(version)
def request(self, method, params=[]):
- if type(method) not in types.StringTypes:
+ if not isinstance(method, str):
raise ValueError('Method name must be a string.')
if not self.id:
self.id = random_id()
@@ -490,10 +465,9 @@ def dumps(
the rpcid argument since the 2.0 spec requires it for responses.
"""
if not version:
- version = config.version
- valid_params = (types.TupleType, types.ListType, types.DictType)
- if methodname in types.StringTypes and \
- type(params) not in valid_params and \
+ version = Config.instance().version
+ if isinstance(methodname, str) and \
+ not isinstance(params, (tuple, list, dict)) and \
not isinstance(params, Fault):
"""
If a method, and params are not in a listish or a Fault,
@@ -505,17 +479,17 @@ def dumps(
payload = Payload(rpcid=rpcid, version=version)
if not encoding:
encoding = 'utf-8'
- if type(params) is Fault:
+ if isinstance(params, Fault):
response = payload.error(params.faultCode, params.faultString)
return jdumps(response, encoding=encoding)
- if type(methodname) not in types.StringTypes and \
+ if not isinstance(methodname, str) 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:
+ if Config.instance().use_jsonclass is True:
from jsonrpclib import jsonclass
params = jsonclass.dump(params)
if methodresponse is True:
@@ -544,7 +518,7 @@ def loads(data):
# 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 is True:
+ if Config.instance().use_jsonclass is True:
from jsonrpclib import jsonclass
result = jsonclass.load(result)
return result
@@ -559,7 +533,8 @@ def check_for_errors(result):
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():
+ 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'] is not None:
code = result['error']['code']
@@ -569,7 +544,7 @@ def check_for_errors(result):
def isbatch(result):
- if type(result) not in (types.ListType, types.TupleType):
+ if not isinstance(result, (list, tuple)):
return False
if len(result) < 1:
return False
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..4cadba8
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,6 @@
+[build-system]
+requires = [
+ "setuptools>=42",
+ "wheel"
+]
+build-backend = "setuptools.build_meta"
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..13d6b31
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,13 @@
+[metadata]
+name = jsonrpclib
+version = 0.2.1
+author = Josh Marshall
+author_email = catchjosh@gmail.com
+url = https://github.com/joshmarshall/jsonrpclib
+long_description = file: README.md
+long_description_content_type = text/markdown
+description = Implementation of the JSON-RPC v2.0 specification (backwards-compatible) as a client library.
+
+[options]
+packages = jsonrpclib
+python_requires = >=3.5
diff --git a/setup.py b/setup.py
deleted file mode 100755
index 1973208..0000000
--- a/setup.py
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env python
-"""
-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
-
-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.
-"""
-
-import distutils.core
-import os
-
-long_description = "Placeholder in case of missing README.md."
-
-if os.path.exists("README.md"):
- with open("README.md") as readme_fp:
- long_description = readme_fp.read()
-
-distutils.core.setup(
- name="jsonrpclib",
- version="0.1.7",
- packages=["jsonrpclib"],
- author="Josh Marshall",
- author_email="catchjosh@gmail.com",
- url="http://github.com/joshmarshall/jsonrpclib/",
- license="http://www.apache.org/licenses/LICENSE-2.0",
- description="This project is an implementation of the JSON-RPC v2.0 " +
- "specification (backwards-compatible) as a client library.",
- long_description=long_description)
diff --git a/tests.py b/tests.py
index e241104..9a104e0 100644
--- a/tests.py
+++ b/tests.py
@@ -36,6 +36,9 @@ from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCRequestHandler
+ORIGINAL_HISTORY_SIZE = history.size
+
+
def get_port(family=socket.AF_INET):
sock = socket.socket(family, socket.SOCK_STREAM)
sock.bind(("localhost", 0))
@@ -53,6 +56,10 @@ class TestCompatibility(unittest.TestCase):
self.server = server_set_up(addr=('', self.port))
self.client = Server('http://localhost:%d' % self.port)
+ def tearDown(self):
+ self.server.stop()
+ self.server.join()
+
# v1 tests forthcoming
# Version 2.0 Tests
@@ -281,6 +288,13 @@ class InternalTests(unittest.TestCase):
def setUp(self):
self.port = get_port()
self.server = server_set_up(addr=('', self.port))
+ self.addCleanup(self.cleanup)
+
+ def cleanup(self):
+ self.server.stop()
+ self.server.join()
+ history.size = ORIGINAL_HISTORY_SIZE
+ history.clear()
def get_client(self):
return Server('http://localhost:%d' % self.port)
@@ -316,7 +330,10 @@ class InternalTests(unittest.TestCase):
def test_single_namespace(self):
client = self.get_client()
+
response = client.namespace.sum(1, 2, 4)
+ self.assertEqual(7, response)
+
request = json.loads(history.request)
response = json.loads(history.response)
verify_request = {
@@ -328,8 +345,64 @@ class InternalTests(unittest.TestCase):
}
verify_request['id'] = request['id']
verify_response['id'] = request['id']
- self.assertTrue(verify_request == request)
- self.assertTrue(verify_response == response)
+ self.assertEqual(verify_request, request)
+ self.assertEqual(verify_response, response)
+
+ def test_history_defaults_to_20(self):
+ client = self.get_client()
+ self.assertEqual(20, history.size)
+
+ for i in range(30):
+ client.namespace.sum(i, i)
+
+ self.assertEqual(20, len(history.requests))
+ self.assertEqual(20, len(history.responses))
+
+ verify_request = {
+ "jsonrpc": "2.0", "params": [29, 29],
+ "method": "namespace.sum"
+ }
+ verify_response = {"jsonrpc": "2.0", "result": 58}
+
+ # it should truncate to the *last* 20
+ request = json.loads(history.request)
+ response = json.loads(history.response)
+
+ verify_request["id"] = request["id"]
+ self.assertEqual(request, verify_request)
+
+ verify_response["id"] = request["id"]
+ self.assertEqual(response, verify_response)
+
+ def test_history_allows_configurable_size(self):
+ client = self.get_client()
+ history.size = 10
+
+ for i in range(30):
+ client.namespace.sum(i, i)
+
+ self.assertEqual(10, len(history.requests))
+ self.assertEqual(10, len(history.responses))
+
+ def test_history_allows_unlimited_size(self):
+ client = self.get_client()
+ history.size = -1
+
+ for i in range(40):
+ client.namespace.sum(i, i)
+
+ self.assertEqual(40, len(history.requests))
+ self.assertEqual(40, len(history.responses))
+
+ def test_history_can_be_disabled(self):
+ client = self.get_client()
+ history.size = 0
+
+ for i in range(40):
+ client.namespace.sum(i, i)
+
+ self.assertEqual(0, len(history.requests))
+ self.assertEqual(0, len(history.responses))
def test_multicall_success(self):
multicall = self.get_multicall_client()
@@ -365,37 +438,40 @@ class InternalTests(unittest.TestCase):
result = sub_service_proxy.add(21, 21)
self.assertTrue(result == 42)
-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,
- but over a Unix socket instead of a TCP socket.
- """
- def setUp(self):
- 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,
- address_family=socket.AF_UNIX
- )
+@unittest.skipIf(
+ not jsonrpc.USE_UNIX_SOCKETS or "SKIP_UNIX_SOCKET_TESTS" in os.environ,
+ "Skipping Unix socket tests -- unsupported in this environment.")
+class UnixSocketInternalTests(InternalTests):
+ """
+ These tests run the same internal communication tests,
+ but over a Unix socket instead of a TCP socket.
+ """
+ def setUp(self):
+ super().setUp()
+ suffix = "%d.sock" % get_port()
- def get_client(self):
- return Server('unix:/%s' % self.port)
+ # Open to safer, alternative processes
+ # for getting a temp file name...
+ temp = tempfile.NamedTemporaryFile(
+ suffix=suffix
+ )
+ self.port = temp.name
+ temp.close()
- def tearDown(self):
- """ Removes the tempory socket file """
- os.unlink(self.port)
+ self.server = server_set_up(
+ 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 """
+ self.server.stop()
+ self.server.join()
+ os.unlink(self.port)
class UnixSocketErrorTests(unittest.TestCase):
@@ -473,7 +549,12 @@ def server_set_up(addr, address_family=socket.AF_INET):
server.register_function(service.summation, 'sum')
server.register_function(service.summation, 'notify_sum')
server.register_function(service.summation, 'namespace.sum')
+
+ def stop():
+ server.shutdown()
+
server_proc = Thread(target=server.serve_forever)
server_proc.daemon = True
+ server_proc.stop = stop
server_proc.start()
return server_proc
diff --git a/tox.ini b/tox.ini
index 36d7f18..a6529eb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,10 @@
[tox]
-envlist = py26,py27
+envlist = py35,py36,py37,py38,py39
+isolated_build = True
+
[testenv]
deps= -rdev-requirements.txt
-commands=nosetests tests.py --with-coverage --cover-package=jsonrpclib
+whitelist_externals=mkdir
+commands=mkdir -p reports/{envname}
+ coverage run --source jsonrpclib -m pytest --junitxml=reports/{envname}/test-results.xml tests.py
+ coverage report