Merge pyextra subtree

This commit is contained in:
Vehicle Researcher
2019-03-26 01:03:51 -07:00
69 changed files with 8796 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
from .manager import JSONRPCResponseManager
from .dispatcher import Dispatcher
__version = (1, 12, 1)
__version__ = version = '.'.join(map(str, __version))
__project__ = PROJECT = __name__
dispatcher = Dispatcher()
# lint_ignore=W0611,W0401
View File
+89
View File
@@ -0,0 +1,89 @@
from __future__ import absolute_import
from django.views.decorators.csrf import csrf_exempt
from django.conf.urls import url
from django.conf import settings
from django.http import HttpResponse, HttpResponseNotAllowed
import copy
import json
import logging
import time
from ..exceptions import JSONRPCInvalidRequestException
from ..jsonrpc import JSONRPCRequest
from ..manager import JSONRPCResponseManager
from ..utils import DatetimeDecimalEncoder
from ..dispatcher import Dispatcher
logger = logging.getLogger(__name__)
def response_serialize(obj):
""" Serializes response's data object to JSON. """
return json.dumps(obj, cls=DatetimeDecimalEncoder)
class JSONRPCAPI(object):
def __init__(self, dispatcher=None):
self.dispatcher = dispatcher if dispatcher is not None \
else Dispatcher()
@property
def urls(self):
urls = [
url(r'^$', self.jsonrpc, name='endpoint'),
]
if getattr(settings, 'JSONRPC_MAP_VIEW_ENABLED', settings.DEBUG):
urls.append(
url(r'^map$', self.jsonrpc_map, name='map')
)
return urls
@csrf_exempt
def jsonrpc(self, request):
""" JSON-RPC 2.0 handler."""
if request.method != "POST":
return HttpResponseNotAllowed(["POST"])
request_str = request.body.decode('utf8')
try:
jsonrpc_request = JSONRPCRequest.from_json(request_str)
except (TypeError, ValueError, JSONRPCInvalidRequestException):
response = JSONRPCResponseManager.handle(
request_str, self.dispatcher)
else:
jsonrpc_request.params = jsonrpc_request.params or {}
jsonrpc_request_params = copy.copy(jsonrpc_request.params)
if isinstance(jsonrpc_request.params, dict):
jsonrpc_request.params.update(request=request)
t1 = time.time()
response = JSONRPCResponseManager.handle_request(
jsonrpc_request, self.dispatcher)
t2 = time.time()
logger.info('{0}({1}) {2:.2f} sec'.format(
jsonrpc_request.method, jsonrpc_request_params, t2 - t1))
if response:
response.serialize = response_serialize
response = response.json
return HttpResponse(response, content_type="application/json")
def jsonrpc_map(self, request):
""" Map of json-rpc available calls.
:return str:
"""
result = "<h1>JSON-RPC map</h1><pre>{0}</pre>".format("\n\n".join([
"{0}: {1}".format(fname, f.__doc__)
for fname, f in self.dispatcher.items()
]))
return HttpResponse(result)
api = JSONRPCAPI()
+85
View File
@@ -0,0 +1,85 @@
from __future__ import absolute_import
import copy
import json
import logging
import time
from uuid import uuid4
from flask import Blueprint, request, Response
from ..exceptions import JSONRPCInvalidRequestException
from ..jsonrpc import JSONRPCRequest
from ..manager import JSONRPCResponseManager
from ..utils import DatetimeDecimalEncoder
from ..dispatcher import Dispatcher
logger = logging.getLogger(__name__)
class JSONRPCAPI(object):
def __init__(self, dispatcher=None, check_content_type=True):
"""
:param dispatcher: methods dispatcher
:param check_content_type: if True - content-type must be
"application/json"
:return:
"""
self.dispatcher = dispatcher if dispatcher is not None \
else Dispatcher()
self.check_content_type = check_content_type
def as_blueprint(self, name=None):
blueprint = Blueprint(name if name else str(uuid4()), __name__)
blueprint.add_url_rule(
'/', view_func=self.jsonrpc, methods=['POST'])
blueprint.add_url_rule(
'/map', view_func=self.jsonrpc_map, methods=['GET'])
return blueprint
def as_view(self):
return self.jsonrpc
def jsonrpc(self):
request_str = self._get_request_str()
try:
jsonrpc_request = JSONRPCRequest.from_json(request_str)
except (TypeError, ValueError, JSONRPCInvalidRequestException):
response = JSONRPCResponseManager.handle(
request_str, self.dispatcher)
else:
response = JSONRPCResponseManager.handle_request(
jsonrpc_request, self.dispatcher)
if response:
response.serialize = self._serialize
response = response.json
return Response(response, content_type="application/json")
def jsonrpc_map(self):
""" Map of json-rpc available calls.
:return str:
"""
result = "<h1>JSON-RPC map</h1><pre>{0}</pre>".format("\n\n".join([
"{0}: {1}".format(fname, f.__doc__)
for fname, f in self.dispatcher.items()
]))
return Response(result)
def _get_request_str(self):
if self.check_content_type or request.data:
return request.data
return list(request.form.keys())[0]
@staticmethod
def _serialize(s):
return json.dumps(s, cls=DatetimeDecimalEncoder)
api = JSONRPCAPI()
+87
View File
@@ -0,0 +1,87 @@
from .utils import JSONSerializable
class JSONRPCBaseRequest(JSONSerializable):
""" Base class for JSON-RPC 1.0 and JSON-RPC 2.0 requests."""
def __init__(self, method=None, params=None, _id=None,
is_notification=None):
self.data = dict()
self.method = method
self.params = params
self._id = _id
self.is_notification = is_notification
@property
def data(self):
return self._data
@data.setter
def data(self, value):
if not isinstance(value, dict):
raise ValueError("data should be dict")
self._data = value
@property
def args(self):
""" Method position arguments.
:return tuple args: method position arguments.
"""
return tuple(self.params) if isinstance(self.params, list) else ()
@property
def kwargs(self):
""" Method named arguments.
:return dict kwargs: method named arguments.
"""
return self.params if isinstance(self.params, dict) else {}
@property
def json(self):
return self.serialize(self.data)
class JSONRPCBaseResponse(JSONSerializable):
""" Base class for JSON-RPC 1.0 and JSON-RPC 2.0 responses."""
def __init__(self, **kwargs):
self.data = dict()
try:
self.result = kwargs['result']
except KeyError:
pass
try:
self.error = kwargs['error']
except KeyError:
pass
self._id = kwargs.get('_id')
if 'result' not in kwargs and 'error' not in kwargs:
raise ValueError("Either result or error should be used")
self.request = None # type: JSONRPCBaseRequest
@property
def data(self):
return self._data
@data.setter
def data(self, value):
if not isinstance(value, dict):
raise ValueError("data should be dict")
self._data = value
@property
def json(self):
return self.serialize(self.data)
+132
View File
@@ -0,0 +1,132 @@
""" Dispatcher is used to add methods (functions) to the server.
For usage examples see :meth:`Dispatcher.add_method`
"""
import functools
import collections
class Dispatcher(collections.MutableMapping):
""" Dictionary like object which maps method_name to method."""
def __init__(self, prototype=None):
""" Build method dispatcher.
Parameters
----------
prototype : object or dict, optional
Initial method mapping.
Examples
--------
Init object with method dictionary.
>>> Dispatcher({"sum": lambda a, b: a + b})
None
"""
self.method_map = dict()
if prototype is not None:
self.build_method_map(prototype)
def __getitem__(self, key):
return self.method_map[key]
def __setitem__(self, key, value):
self.method_map[key] = value
def __delitem__(self, key):
del self.method_map[key]
def __len__(self):
return len(self.method_map)
def __iter__(self):
return iter(self.method_map)
def __repr__(self):
return repr(self.method_map)
def add_class(self, cls):
prefix = cls.__name__.lower() + '.'
self.build_method_map(cls(), prefix)
def add_object(self, obj):
prefix = obj.__class__.__name__.lower() + '.'
self.build_method_map(obj, prefix)
def add_dict(self, dict, prefix=''):
if prefix:
prefix += '.'
self.build_method_map(dict, prefix)
def add_method(self, f=None, name=None):
""" Add a method to the dispatcher.
Parameters
----------
f : callable
Callable to be added.
name : str, optional
Name to register (the default is function **f** name)
Notes
-----
When used as a decorator keeps callable object unmodified.
Examples
--------
Use as method
>>> d = Dispatcher()
>>> d.add_method(lambda a, b: a + b, name="sum")
<function __main__.<lambda>>
Or use as decorator
>>> d = Dispatcher()
>>> @d.add_method
def mymethod(*args, **kwargs):
print(args, kwargs)
Or use as a decorator with a different function name
>>> d = Dispatcher()
>>> @d.add_method(name="my.method")
def mymethod(*args, **kwargs):
print(args, kwargs)
"""
if name and not f:
return functools.partial(self.add_method, name=name)
self.method_map[name or f.__name__] = f
return f
def build_method_map(self, prototype, prefix=''):
""" Add prototype methods to the dispatcher.
Parameters
----------
prototype : object or dict
Initial method mapping.
If given prototype is a dictionary then all callable objects will
be added to dispatcher.
If given prototype is an object then all public methods will
be used.
prefix: string, optional
Prefix of methods
"""
if not isinstance(prototype, dict):
prototype = dict((method, getattr(prototype, method))
for method in dir(prototype)
if not method.startswith('_'))
for attr, method in prototype.items():
if callable(method):
self[prefix + attr] = method
+185
View File
@@ -0,0 +1,185 @@
""" JSON-RPC Exceptions."""
from . import six
import json
class JSONRPCError(object):
""" Error for JSON-RPC communication.
When a rpc call encounters an error, the Response Object MUST contain the
error member with a value that is a Object with the following members:
Parameters
----------
code: int
A Number that indicates the error type that occurred.
This MUST be an integer.
The error codes from and including -32768 to -32000 are reserved for
pre-defined errors. Any code within this range, but not defined
explicitly below is reserved for future use. The error codes are nearly
the same as those suggested for XML-RPC at the following
url: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
message: str
A String providing a short description of the error.
The message SHOULD be limited to a concise single sentence.
data: int or str or dict or list, optional
A Primitive or Structured value that contains additional
information about the error.
This may be omitted.
The value of this member is defined by the Server (e.g. detailed error
information, nested errors etc.).
"""
serialize = staticmethod(json.dumps)
deserialize = staticmethod(json.loads)
def __init__(self, code=None, message=None, data=None):
self._data = dict()
self.code = getattr(self.__class__, "CODE", code)
self.message = getattr(self.__class__, "MESSAGE", message)
self.data = data
def __get_code(self):
return self._data["code"]
def __set_code(self, value):
if not isinstance(value, six.integer_types):
raise ValueError("Error code should be integer")
self._data["code"] = value
code = property(__get_code, __set_code)
def __get_message(self):
return self._data["message"]
def __set_message(self, value):
if not isinstance(value, six.string_types):
raise ValueError("Error message should be string")
self._data["message"] = value
message = property(__get_message, __set_message)
def __get_data(self):
return self._data.get("data")
def __set_data(self, value):
if value is not None:
self._data["data"] = value
data = property(__get_data, __set_data)
@classmethod
def from_json(cls, json_str):
data = cls.deserialize(json_str)
return cls(
code=data["code"], message=data["message"], data=data.get("data"))
@property
def json(self):
return self.serialize(self._data)
class JSONRPCParseError(JSONRPCError):
""" Parse Error.
Invalid JSON was received by the server.
An error occurred on the server while parsing the JSON text.
"""
CODE = -32700
MESSAGE = "Parse error"
class JSONRPCInvalidRequest(JSONRPCError):
""" Invalid Request.
The JSON sent is not a valid Request object.
"""
CODE = -32600
MESSAGE = "Invalid Request"
class JSONRPCMethodNotFound(JSONRPCError):
""" Method not found.
The method does not exist / is not available.
"""
CODE = -32601
MESSAGE = "Method not found"
class JSONRPCInvalidParams(JSONRPCError):
""" Invalid params.
Invalid method parameter(s).
"""
CODE = -32602
MESSAGE = "Invalid params"
class JSONRPCInternalError(JSONRPCError):
""" Internal error.
Internal JSON-RPC error.
"""
CODE = -32603
MESSAGE = "Internal error"
class JSONRPCServerError(JSONRPCError):
""" Server error.
Reserved for implementation-defined server-errors.
"""
CODE = -32000
MESSAGE = "Server error"
class JSONRPCException(Exception):
""" JSON-RPC Exception."""
pass
class JSONRPCInvalidRequestException(JSONRPCException):
""" Request is not valid."""
pass
class JSONRPCDispatchException(JSONRPCException):
""" JSON-RPC Dispatch Exception.
Should be thrown in dispatch methods.
"""
def __init__(self, code=None, message=None, data=None, *args, **kwargs):
super(JSONRPCDispatchException, self).__init__(args, kwargs)
self.error = JSONRPCError(code=code, data=data, message=message)
+28
View File
@@ -0,0 +1,28 @@
""" JSON-RPC wrappers for version 1.0 and 2.0.
Objects diring init operation try to choose JSON-RPC 2.0 and in case of error
JSON-RPC 1.0.
from_json methods could decide what format is it by presence of 'jsonrpc'
attribute.
"""
from .utils import JSONSerializable
from .jsonrpc1 import JSONRPC10Request
from .jsonrpc2 import JSONRPC20Request
class JSONRPCRequest(JSONSerializable):
""" JSONRPC Request."""
@classmethod
def from_json(cls, json_str):
data = cls.deserialize(json_str)
return cls.from_data(data)
@classmethod
def from_data(cls, data):
if isinstance(data, dict) and "jsonrpc" not in data:
return JSONRPC10Request.from_data(data)
else:
return JSONRPC20Request.from_data(data)
+151
View File
@@ -0,0 +1,151 @@
from . import six
from .base import JSONRPCBaseRequest, JSONRPCBaseResponse
from .exceptions import JSONRPCInvalidRequestException, JSONRPCError
class JSONRPC10Request(JSONRPCBaseRequest):
""" JSON-RPC 1.0 Request.
A remote method is invoked by sending a request to a remote service.
The request is a single object serialized using json.
:param str method: The name of the method to be invoked.
:param list params: An Array of objects to pass as arguments to the method.
:param _id: This can be of any type. It is used to match the response with
the request that it is replying to.
:param bool is_notification: whether request notification or not.
"""
JSONRPC_VERSION = "1.0"
REQUIRED_FIELDS = set(["method", "params", "id"])
POSSIBLE_FIELDS = set(["method", "params", "id"])
@property
def data(self):
data = dict((k, v) for k, v in self._data.items())
data["id"] = None if self.is_notification else data["id"]
return data
@data.setter
def data(self, value):
if not isinstance(value, dict):
raise ValueError("data should be dict")
self._data = value
@property
def method(self):
return self._data.get("method")
@method.setter
def method(self, value):
if not isinstance(value, six.string_types):
raise ValueError("Method should be string")
self._data["method"] = str(value)
@property
def params(self):
return self._data.get("params")
@params.setter
def params(self, value):
if not isinstance(value, (list, tuple)):
raise ValueError("Incorrect params {0}".format(value))
self._data["params"] = list(value)
@property
def _id(self):
return self._data.get("id")
@_id.setter
def _id(self, value):
self._data["id"] = value
@property
def is_notification(self):
return self._data["id"] is None or self._is_notification
@is_notification.setter
def is_notification(self, value):
if value is None:
value = self._id is None
if self._id is None and not value:
raise ValueError("Can not set attribute is_notification. " +
"Request id should not be None")
self._is_notification = value
@classmethod
def from_json(cls, json_str):
data = cls.deserialize(json_str)
return cls.from_data(data)
@classmethod
def from_data(cls, data):
if not isinstance(data, dict):
raise ValueError("data should be dict")
if cls.REQUIRED_FIELDS <= set(data.keys()) <= cls.POSSIBLE_FIELDS:
return cls(
method=data["method"], params=data["params"], _id=data["id"]
)
else:
extra = set(data.keys()) - cls.POSSIBLE_FIELDS
missed = cls.REQUIRED_FIELDS - set(data.keys())
msg = "Invalid request. Extra fields: {0}, Missed fields: {1}"
raise JSONRPCInvalidRequestException(msg.format(extra, missed))
class JSONRPC10Response(JSONRPCBaseResponse):
JSONRPC_VERSION = "1.0"
@property
def data(self):
data = dict((k, v) for k, v in self._data.items())
return data
@data.setter
def data(self, value):
if not isinstance(value, dict):
raise ValueError("data should be dict")
self._data = value
@property
def result(self):
return self._data.get("result")
@result.setter
def result(self, value):
if self.error:
raise ValueError("Either result or error should be used")
self._data["result"] = value
@property
def error(self):
return self._data.get("error")
@error.setter
def error(self, value):
self._data.pop('value', None)
if value:
self._data["error"] = value
# Test error
JSONRPCError(**value)
@property
def _id(self):
return self._data.get("id")
@_id.setter
def _id(self, value):
if value is None:
raise ValueError("id could not be null for JSON-RPC1.0 Response")
self._data["id"] = value
+267
View File
@@ -0,0 +1,267 @@
from . import six
import json
from .exceptions import JSONRPCError, JSONRPCInvalidRequestException
from .base import JSONRPCBaseRequest, JSONRPCBaseResponse
class JSONRPC20Request(JSONRPCBaseRequest):
""" A rpc call is represented by sending a Request object to a Server.
:param str method: A String containing the name of the method to be
invoked. Method names that begin with the word rpc followed by a
period character (U+002E or ASCII 46) are reserved for rpc-internal
methods and extensions and MUST NOT be used for anything else.
:param params: A Structured value that holds the parameter values to be
used during the invocation of the method. This member MAY be omitted.
:type params: iterable or dict
:param _id: An identifier established by the Client that MUST contain a
String, Number, or NULL value if included. If it is not included it is
assumed to be a notification. The value SHOULD normally not be Null
[1] and Numbers SHOULD NOT contain fractional parts [2].
:type _id: str or int or None
:param bool is_notification: Whether request is notification or not. If
value is True, _id is not included to request. It allows to create
requests with id = null.
The Server MUST reply with the same value in the Response object if
included. This member is used to correlate the context between the two
objects.
[1] The use of Null as a value for the id member in a Request object is
discouraged, because this specification uses a value of Null for Responses
with an unknown id. Also, because JSON-RPC 1.0 uses an id value of Null
for Notifications this could cause confusion in handling.
[2] Fractional parts may be problematic, since many decimal fractions
cannot be represented exactly as binary fractions.
"""
JSONRPC_VERSION = "2.0"
REQUIRED_FIELDS = set(["jsonrpc", "method"])
POSSIBLE_FIELDS = set(["jsonrpc", "method", "params", "id"])
@property
def data(self):
data = dict(
(k, v) for k, v in self._data.items()
if not (k == "id" and self.is_notification)
)
data["jsonrpc"] = self.JSONRPC_VERSION
return data
@data.setter
def data(self, value):
if not isinstance(value, dict):
raise ValueError("data should be dict")
self._data = value
@property
def method(self):
return self._data.get("method")
@method.setter
def method(self, value):
if not isinstance(value, six.string_types):
raise ValueError("Method should be string")
if value.startswith("rpc."):
raise ValueError(
"Method names that begin with the word rpc followed by a " +
"period character (U+002E or ASCII 46) are reserved for " +
"rpc-internal methods and extensions and MUST NOT be used " +
"for anything else.")
self._data["method"] = str(value)
@property
def params(self):
return self._data.get("params")
@params.setter
def params(self, value):
if value is not None and not isinstance(value, (list, tuple, dict)):
raise ValueError("Incorrect params {0}".format(value))
value = list(value) if isinstance(value, tuple) else value
if value is not None:
self._data["params"] = value
@property
def _id(self):
return self._data.get("id")
@_id.setter
def _id(self, value):
if value is not None and \
not isinstance(value, six.string_types + six.integer_types):
raise ValueError("id should be string or integer")
self._data["id"] = value
@classmethod
def from_json(cls, json_str):
data = cls.deserialize(json_str)
return cls.from_data(data)
@classmethod
def from_data(cls, data):
is_batch = isinstance(data, list)
data = data if is_batch else [data]
if not data:
raise JSONRPCInvalidRequestException("[] value is not accepted")
if not all(isinstance(d, dict) for d in data):
raise JSONRPCInvalidRequestException(
"Each request should be an object (dict)")
result = []
for d in data:
if not cls.REQUIRED_FIELDS <= set(d.keys()) <= cls.POSSIBLE_FIELDS:
extra = set(d.keys()) - cls.POSSIBLE_FIELDS
missed = cls.REQUIRED_FIELDS - set(d.keys())
msg = "Invalid request. Extra fields: {0}, Missed fields: {1}"
raise JSONRPCInvalidRequestException(msg.format(extra, missed))
try:
result.append(JSONRPC20Request(
method=d["method"], params=d.get("params"),
_id=d.get("id"), is_notification="id" not in d,
))
except ValueError as e:
raise JSONRPCInvalidRequestException(str(e))
return JSONRPC20BatchRequest(*result) if is_batch else result[0]
class JSONRPC20BatchRequest(object):
""" Batch JSON-RPC 2.0 Request.
:param JSONRPC20Request *requests: requests
"""
JSONRPC_VERSION = "2.0"
def __init__(self, *requests):
self.requests = requests
@classmethod
def from_json(cls, json_str):
return JSONRPC20Request.from_json(json_str)
@property
def json(self):
return json.dumps([r.data for r in self.requests])
def __iter__(self):
return iter(self.requests)
class JSONRPC20Response(JSONRPCBaseResponse):
""" JSON-RPC response object to JSONRPC20Request.
When a rpc call is made, the Server MUST reply with a Response, except for
in the case of Notifications. The Response is expressed as a single JSON
Object, with the following members:
:param str jsonrpc: A String specifying the version of the JSON-RPC
protocol. MUST be exactly "2.0".
:param result: This member is REQUIRED on success.
This member MUST NOT exist if there was an error invoking the method.
The value of this member is determined by the method invoked on the
Server.
:param dict error: This member is REQUIRED on error.
This member MUST NOT exist if there was no error triggered during
invocation. The value for this member MUST be an Object.
:param id: This member is REQUIRED.
It MUST be the same as the value of the id member in the Request
Object. If there was an error in detecting the id in the Request
object (e.g. Parse error/Invalid Request), it MUST be Null.
:type id: str or int or None
Either the result member or error member MUST be included, but both
members MUST NOT be included.
"""
JSONRPC_VERSION = "2.0"
@property
def data(self):
data = dict((k, v) for k, v in self._data.items())
data["jsonrpc"] = self.JSONRPC_VERSION
return data
@data.setter
def data(self, value):
if not isinstance(value, dict):
raise ValueError("data should be dict")
self._data = value
@property
def result(self):
return self._data.get("result")
@result.setter
def result(self, value):
if self.error:
raise ValueError("Either result or error should be used")
self._data["result"] = value
@property
def error(self):
return self._data.get("error")
@error.setter
def error(self, value):
self._data.pop('value', None)
if value:
self._data["error"] = value
# Test error
JSONRPCError(**value)
@property
def _id(self):
return self._data.get("id")
@_id.setter
def _id(self, value):
if value is not None and \
not isinstance(value, six.string_types + six.integer_types):
raise ValueError("id should be string or integer")
self._data["id"] = value
class JSONRPC20BatchResponse(object):
JSONRPC_VERSION = "2.0"
def __init__(self, *responses):
self.responses = responses
self.request = None # type: JSONRPC20BatchRequest
@property
def data(self):
return [r.data for r in self.responses]
@property
def json(self):
return json.dumps(self.data)
def __iter__(self):
return iter(self.responses)
+136
View File
@@ -0,0 +1,136 @@
import json
import logging
from .utils import is_invalid_params
from .exceptions import (
JSONRPCInvalidParams,
JSONRPCInvalidRequest,
JSONRPCInvalidRequestException,
JSONRPCMethodNotFound,
JSONRPCParseError,
JSONRPCServerError,
JSONRPCDispatchException,
)
from .jsonrpc1 import JSONRPC10Response
from .jsonrpc2 import (
JSONRPC20BatchRequest,
JSONRPC20BatchResponse,
JSONRPC20Response,
)
from .jsonrpc import JSONRPCRequest
logger = logging.getLogger(__name__)
class JSONRPCResponseManager(object):
""" JSON-RPC response manager.
Method brings syntactic sugar into library. Given dispatcher it handles
request (both single and batch) and handles errors.
Request could be handled in parallel, it is server responsibility.
:param str request_str: json string. Will be converted into
JSONRPC20Request, JSONRPC20BatchRequest or JSONRPC10Request
:param dict dispather: dict<function_name:function>.
"""
RESPONSE_CLASS_MAP = {
"1.0": JSONRPC10Response,
"2.0": JSONRPC20Response,
}
@classmethod
def handle(cls, request_str, dispatcher):
if isinstance(request_str, bytes):
request_str = request_str.decode("utf-8")
try:
data = json.loads(request_str)
except (TypeError, ValueError):
return JSONRPC20Response(error=JSONRPCParseError()._data)
try:
request = JSONRPCRequest.from_data(data)
except JSONRPCInvalidRequestException:
return JSONRPC20Response(error=JSONRPCInvalidRequest()._data)
return cls.handle_request(request, dispatcher)
@classmethod
def handle_request(cls, request, dispatcher):
""" Handle request data.
At this moment request has correct jsonrpc format.
:param dict request: data parsed from request_str.
:param jsonrpc.dispatcher.Dispatcher dispatcher:
.. versionadded: 1.8.0
"""
rs = request if isinstance(request, JSONRPC20BatchRequest) \
else [request]
responses = [r for r in cls._get_responses(rs, dispatcher)
if r is not None]
# notifications
if not responses:
return
if isinstance(request, JSONRPC20BatchRequest):
response = JSONRPC20BatchResponse(*responses)
response.request = request
return response
else:
return responses[0]
@classmethod
def _get_responses(cls, requests, dispatcher):
""" Response to each single JSON-RPC Request.
:return iterator(JSONRPC20Response):
.. versionadded: 1.9.0
TypeError inside the function is distinguished from Invalid Params.
"""
for request in requests:
def make_response(**kwargs):
response = cls.RESPONSE_CLASS_MAP[request.JSONRPC_VERSION](
_id=request._id, **kwargs)
response.request = request
return response
output = None
try:
method = dispatcher[request.method]
except KeyError:
output = make_response(error=JSONRPCMethodNotFound()._data)
else:
try:
result = method(*request.args, **request.kwargs)
except JSONRPCDispatchException as e:
output = make_response(error=e.error._data)
except Exception as e:
data = {
"type": e.__class__.__name__,
"args": e.args,
"message": str(e),
}
logger.exception("API Exception: {0}".format(data))
if isinstance(e, TypeError) and is_invalid_params(
method, *request.args, **request.kwargs):
output = make_response(
error=JSONRPCInvalidParams(data=data)._data)
else:
output = make_response(
error=JSONRPCServerError(data=data)._data)
else:
output = make_response(result=result)
finally:
if not request.is_notification:
yield output
+584
View File
@@ -0,0 +1,584 @@
"""Utilities for writing code that runs on Python 2 and 3"""
# Copyright (c) 2010-2013 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.4.1"
# Useful for very coarse version differentiation.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
if sys.platform.startswith("java"):
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
def _add_doc(func, doc):
"""Add documentation to a function."""
func.__doc__ = doc
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
class _LazyDescr(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, tp):
result = self._resolve()
setattr(obj, self.name, result)
# This is a bit ugly, but it avoids running this again.
delattr(tp, self.name)
return result
class MovedModule(_LazyDescr):
def __init__(self, name, old, new=None):
super(MovedModule, self).__init__(name)
if PY3:
if new is None:
new = name
self.mod = new
else:
self.mod = old
def _resolve(self):
return _import_module(self.mod)
class MovedAttribute(_LazyDescr):
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
super(MovedAttribute, self).__init__(name)
if PY3:
if new_mod is None:
new_mod = name
self.mod = new_mod
if new_attr is None:
if old_attr is None:
new_attr = name
else:
new_attr = old_attr
self.attr = new_attr
else:
self.mod = old_mod
if old_attr is None:
old_attr = name
self.attr = old_attr
def _resolve(self):
module = _import_module(self.mod)
return getattr(module, self.attr)
class _MovedItems(types.ModuleType):
"""Lazy loading of moved objects"""
_moved_attributes = [
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
MovedAttribute("StringIO", "StringIO", "io"),
MovedAttribute("UserString", "UserString", "collections"),
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
MovedModule("copyreg", "copy_reg"),
MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"),
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
MovedModule("cPickle", "cPickle", "pickle"),
MovedModule("queue", "Queue"),
MovedModule("reprlib", "repr"),
MovedModule("socketserver", "SocketServer"),
MovedModule("_thread", "thread", "_thread"),
MovedModule("tkinter", "Tkinter"),
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
MovedModule("tkinter_colorchooser", "tkColorChooser",
"tkinter.colorchooser"),
MovedModule("tkinter_commondialog", "tkCommonDialog",
"tkinter.commondialog"),
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
"tkinter.simpledialog"),
MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
MovedModule("winreg", "_winreg"),
]
for attr in _moved_attributes:
setattr(_MovedItems, attr.name, attr)
del attr
moves = sys.modules[__name__ + ".moves"] = _MovedItems(__name__ + ".moves")
class Module_six_moves_urllib_parse(types.ModuleType):
"""Lazy loading of moved objects in six.moves.urllib_parse"""
_urllib_parse_moved_attributes = [
MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
MovedAttribute("urljoin", "urlparse", "urllib.parse"),
MovedAttribute("urlparse", "urlparse", "urllib.parse"),
MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
MovedAttribute("quote", "urllib", "urllib.parse"),
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
MovedAttribute("unquote", "urllib", "urllib.parse"),
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
MovedAttribute("urlencode", "urllib", "urllib.parse"),
]
for attr in _urllib_parse_moved_attributes:
setattr(Module_six_moves_urllib_parse, attr.name, attr)
del attr
sys.modules[__name__ + ".moves.urllib_parse"] = Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse")
sys.modules[__name__ + ".moves.urllib.parse"] = Module_six_moves_urllib_parse(__name__ + ".moves.urllib.parse")
class Module_six_moves_urllib_error(types.ModuleType):
"""Lazy loading of moved objects in six.moves.urllib_error"""
_urllib_error_moved_attributes = [
MovedAttribute("URLError", "urllib2", "urllib.error"),
MovedAttribute("HTTPError", "urllib2", "urllib.error"),
MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
]
for attr in _urllib_error_moved_attributes:
setattr(Module_six_moves_urllib_error, attr.name, attr)
del attr
sys.modules[__name__ + ".moves.urllib_error"] = Module_six_moves_urllib_error(__name__ + ".moves.urllib_error")
sys.modules[__name__ + ".moves.urllib.error"] = Module_six_moves_urllib_error(__name__ + ".moves.urllib.error")
class Module_six_moves_urllib_request(types.ModuleType):
"""Lazy loading of moved objects in six.moves.urllib_request"""
_urllib_request_moved_attributes = [
MovedAttribute("urlopen", "urllib2", "urllib.request"),
MovedAttribute("install_opener", "urllib2", "urllib.request"),
MovedAttribute("build_opener", "urllib2", "urllib.request"),
MovedAttribute("pathname2url", "urllib", "urllib.request"),
MovedAttribute("url2pathname", "urllib", "urllib.request"),
MovedAttribute("getproxies", "urllib", "urllib.request"),
MovedAttribute("Request", "urllib2", "urllib.request"),
MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
MovedAttribute("FileHandler", "urllib2", "urllib.request"),
MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
MovedAttribute("urlretrieve", "urllib", "urllib.request"),
MovedAttribute("urlcleanup", "urllib", "urllib.request"),
MovedAttribute("URLopener", "urllib", "urllib.request"),
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
]
for attr in _urllib_request_moved_attributes:
setattr(Module_six_moves_urllib_request, attr.name, attr)
del attr
sys.modules[__name__ + ".moves.urllib_request"] = Module_six_moves_urllib_request(__name__ + ".moves.urllib_request")
sys.modules[__name__ + ".moves.urllib.request"] = Module_six_moves_urllib_request(__name__ + ".moves.urllib.request")
class Module_six_moves_urllib_response(types.ModuleType):
"""Lazy loading of moved objects in six.moves.urllib_response"""
_urllib_response_moved_attributes = [
MovedAttribute("addbase", "urllib", "urllib.response"),
MovedAttribute("addclosehook", "urllib", "urllib.response"),
MovedAttribute("addinfo", "urllib", "urllib.response"),
MovedAttribute("addinfourl", "urllib", "urllib.response"),
]
for attr in _urllib_response_moved_attributes:
setattr(Module_six_moves_urllib_response, attr.name, attr)
del attr
sys.modules[__name__ + ".moves.urllib_response"] = Module_six_moves_urllib_response(__name__ + ".moves.urllib_response")
sys.modules[__name__ + ".moves.urllib.response"] = Module_six_moves_urllib_response(__name__ + ".moves.urllib.response")
class Module_six_moves_urllib_robotparser(types.ModuleType):
"""Lazy loading of moved objects in six.moves.urllib_robotparser"""
_urllib_robotparser_moved_attributes = [
MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
]
for attr in _urllib_robotparser_moved_attributes:
setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
del attr
sys.modules[__name__ + ".moves.urllib_robotparser"] = Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib_robotparser")
sys.modules[__name__ + ".moves.urllib.robotparser"] = Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser")
class Module_six_moves_urllib(types.ModuleType):
"""Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
parse = sys.modules[__name__ + ".moves.urllib_parse"]
error = sys.modules[__name__ + ".moves.urllib_error"]
request = sys.modules[__name__ + ".moves.urllib_request"]
response = sys.modules[__name__ + ".moves.urllib_response"]
robotparser = sys.modules[__name__ + ".moves.urllib_robotparser"]
sys.modules[__name__ + ".moves.urllib"] = Module_six_moves_urllib(__name__ + ".moves.urllib")
def add_move(move):
"""Add an item to six.moves."""
setattr(_MovedItems, move.name, move)
def remove_move(name):
"""Remove item from six.moves."""
try:
delattr(_MovedItems, name)
except AttributeError:
try:
del moves.__dict__[name]
except KeyError:
raise AttributeError("no such move, %r" % (name,))
if PY3:
_meth_func = "__func__"
_meth_self = "__self__"
_func_closure = "__closure__"
_func_code = "__code__"
_func_defaults = "__defaults__"
_func_globals = "__globals__"
_iterkeys = "keys"
_itervalues = "values"
_iteritems = "items"
_iterlists = "lists"
else:
_meth_func = "im_func"
_meth_self = "im_self"
_func_closure = "func_closure"
_func_code = "func_code"
_func_defaults = "func_defaults"
_func_globals = "func_globals"
_iterkeys = "iterkeys"
_itervalues = "itervalues"
_iteritems = "iteritems"
_iterlists = "iterlists"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
try:
callable = callable
except NameError:
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
if PY3:
def get_unbound_function(unbound):
return unbound
create_bound_method = types.MethodType
Iterator = object
else:
def get_unbound_function(unbound):
return unbound.im_func
def create_bound_method(func, obj):
return types.MethodType(func, obj, obj.__class__)
class Iterator(object):
def next(self):
return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
"""Get the function out of a possibly unbound function""")
get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_closure = operator.attrgetter(_func_closure)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
get_function_globals = operator.attrgetter(_func_globals)
def iterkeys(d, **kw):
"""Return an iterator over the keys of a dictionary."""
return iter(getattr(d, _iterkeys)(**kw))
def itervalues(d, **kw):
"""Return an iterator over the values of a dictionary."""
return iter(getattr(d, _itervalues)(**kw))
def iteritems(d, **kw):
"""Return an iterator over the (key, value) pairs of a dictionary."""
return iter(getattr(d, _iteritems)(**kw))
def iterlists(d, **kw):
"""Return an iterator over the (key, [values]) pairs of a dictionary."""
return iter(getattr(d, _iterlists)(**kw))
if PY3:
def b(s):
return s.encode("latin-1")
def u(s):
return s
unichr = chr
if sys.version_info[1] <= 1:
def int2byte(i):
return bytes((i,))
else:
# This is about 2x faster than the implementation above on 3.2+
int2byte = operator.methodcaller("to_bytes", 1, "big")
byte2int = operator.itemgetter(0)
indexbytes = operator.getitem
iterbytes = iter
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
else:
def b(s):
return s
def u(s):
return unicode(s, "unicode_escape")
unichr = unichr
int2byte = chr
def byte2int(bs):
return ord(bs[0])
def indexbytes(buf, i):
return ord(buf[i])
def iterbytes(buf):
return (ord(byte) for byte in buf)
import StringIO
StringIO = BytesIO = StringIO.StringIO
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
if PY3:
exec_ = getattr(moves.builtins, "exec")
def reraise(tp, value, tb=None):
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
else:
def exec_(_code_, _globs_=None, _locs_=None):
"""Execute code in a namespace."""
if _globs_ is None:
frame = sys._getframe(1)
_globs_ = frame.f_globals
if _locs_ is None:
_locs_ = frame.f_locals
del frame
elif _locs_ is None:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
""")
print_ = getattr(moves.builtins, "print", None)
if print_ is None:
def print_(*args, **kwargs):
"""The new-style print function for Python 2.4 and 2.5."""
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
# If the file has an encoding, encode unicode with it.
if (isinstance(fp, file) and
isinstance(data, unicode) and
fp.encoding is not None):
errors = getattr(fp, "errors", None)
if errors is None:
errors = "strict"
data = data.encode(fp.encoding, errors)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
_add_doc(reraise, """Reraise an exception.""")
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
return meta("NewBase", bases, {})
def add_metaclass(metaclass):
"""Class decorator for creating a class with a metaclass."""
def wrapper(cls):
orig_vars = cls.__dict__.copy()
orig_vars.pop('__dict__', None)
orig_vars.pop('__weakref__', None)
for slots_var in orig_vars.get('__slots__', ()):
orig_vars.pop(slots_var)
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper
View File
+7
View File
@@ -0,0 +1,7 @@
# Python3.5+ code.
# This won't even parse in earlier versions, so it's kept in a separate file
# and imported when needed.
def distance(a: float, b: float) -> float:
return (a ** 2 + b ** 2) ** 0.5
@@ -0,0 +1,11 @@
SECRET_KEY = 'secret'
ROOT_URLCONF = 'jsonrpc.tests.test_backend_django.urls'
ALLOWED_HOSTS = ['testserver']
DATABASE_ENGINE = 'django.db.backends.sqlite3'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
JSONRPC_MAP_VIEW_ENABLED = True
@@ -0,0 +1,89 @@
""" Test Django Backend."""
from __future__ import absolute_import
import os
try:
from django.core.urlresolvers import RegexURLPattern
from django.test import TestCase
except ImportError:
import unittest
raise unittest.SkipTest('Django not found for testing')
from ...backend.django import JSONRPCAPI, api
import json
class TestDjangoBackend(TestCase):
@classmethod
def setUpClass(cls):
os.environ['DJANGO_SETTINGS_MODULE'] = \
'jsonrpc.tests.test_backend_django.settings'
super(TestDjangoBackend, cls).setUpClass()
def test_urls(self):
self.assertTrue(isinstance(api.urls, list))
for api_url in api.urls:
self.assertTrue(isinstance(api_url, RegexURLPattern))
def test_client(self):
@api.dispatcher.add_method
def dummy(request):
return ""
json_data = {
"id": "0",
"jsonrpc": "2.0",
"method": "dummy",
}
response = self.client.post(
'',
json.dumps(json_data),
content_type='application/json',
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.content.decode('utf8'))
self.assertEqual(data['result'], '')
def test_method_not_allowed(self):
response = self.client.get(
'',
content_type='application/json',
)
self.assertEqual(response.status_code, 405, "Should allow only POST")
def test_invalid_request(self):
response = self.client.post(
'',
'{',
content_type='application/json',
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.content.decode('utf8'))
self.assertEqual(data['error']['code'], -32700)
self.assertEqual(data['error']['message'], 'Parse error')
def test_resource_map(self):
response = self.client.get('/map')
self.assertEqual(response.status_code, 200)
data = response.content.decode('utf8')
self.assertIn("JSON-RPC map", data)
def test_method_not_allowed_prefix(self):
response = self.client.get(
'/prefix/',
content_type='application/json',
)
self.assertEqual(response.status_code, 405)
def test_resource_map_prefix(self):
response = self.client.get('/prefix/map')
self.assertEqual(response.status_code, 200)
def test_empty_initial_dispatcher(self):
class SubDispatcher(type(api.dispatcher)):
pass
custom_dispatcher = SubDispatcher()
custom_api = JSONRPCAPI(custom_dispatcher)
self.assertEqual(type(custom_api.dispatcher), SubDispatcher)
self.assertEqual(id(custom_api.dispatcher), id(custom_dispatcher))
@@ -0,0 +1,7 @@
from django.conf.urls import url, include
from jsonrpc.backend.django import api
urlpatterns = [
url(r'', include(api.urls)),
url(r'^prefix/', include(api.urls)),
]
@@ -0,0 +1,182 @@
import json
import sys
if sys.version_info < (3, 3):
from mock import patch
else:
from unittest.mock import patch
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest
# Flask is supported only for python2 and python3.3+
if sys.version_info < (3, 0) or sys.version_info >= (3, 3):
try:
from flask import Flask
except ImportError:
raise unittest.SkipTest('Flask not found for testing')
from ...backend.flask import JSONRPCAPI, api
@api.dispatcher.add_method
def dummy():
return ""
@unittest.skipIf((3, 0) <= sys.version_info < (3, 3),
'Flask does not support python 3.0 - 3.2')
class TestFlaskBackend(unittest.TestCase):
REQUEST = json.dumps({
"id": "0",
"jsonrpc": "2.0",
"method": "dummy",
})
def setUp(self):
self.client = self._get_test_client(JSONRPCAPI())
def _get_test_client(self, api):
@api.dispatcher.add_method
def dummy():
return ""
app = Flask(__name__)
app.config["TESTING"] = True
app.register_blueprint(api.as_blueprint())
return app.test_client()
def test_client(self):
response = self.client.post(
'/',
data=self.REQUEST,
content_type='application/json',
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf8'))
self.assertEqual(data['result'], '')
def test_method_not_allowed(self):
response = self.client.get(
'/',
content_type='application/json',
)
self.assertEqual(response.status_code, 405, "Should allow only POST")
def test_parse_error(self):
response = self.client.post(
'/',
data='{',
content_type='application/json',
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf8'))
self.assertEqual(data['error']['code'], -32700)
self.assertEqual(data['error']['message'], 'Parse error')
def test_wrong_content_type(self):
response = self.client.post(
'/',
data=self.REQUEST,
content_type='application/x-www-form-urlencoded',
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf8'))
self.assertEqual(data['error']['code'], -32700)
self.assertEqual(data['error']['message'], 'Parse error')
def test_invalid_request(self):
response = self.client.post(
'/',
data='{"method": "dummy", "id": 1}',
content_type='application/json',
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf8'))
self.assertEqual(data['error']['code'], -32600)
self.assertEqual(data['error']['message'], 'Invalid Request')
def test_method_not_found(self):
data = {
"jsonrpc": "2.0",
"method": "dummy2",
"id": 1
}
response = self.client.post(
'/',
data=json.dumps(data),
content_type='application/json',
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf8'))
self.assertEqual(data['error']['code'], -32601)
self.assertEqual(data['error']['message'], 'Method not found')
def test_invalid_parameters(self):
data = {
"jsonrpc": "2.0",
"method": "dummy",
"params": [42],
"id": 1
}
response = self.client.post(
'/',
data=json.dumps(data),
content_type='application/json',
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf8'))
self.assertEqual(data['error']['code'], -32602)
self.assertEqual(data['error']['message'], 'Invalid params')
def test_resource_map(self):
response = self.client.get('/map')
self.assertEqual(response.status_code, 200)
self.assertTrue("JSON-RPC map" in response.data.decode('utf8'))
def test_method_not_allowed_prefix(self):
response = self.client.get(
'/',
content_type='application/json',
)
self.assertEqual(response.status_code, 405)
def test_resource_map_prefix(self):
response = self.client.get('/map')
self.assertEqual(response.status_code, 200)
def test_as_view(self):
api = JSONRPCAPI()
with patch.object(api, 'jsonrpc') as mock_jsonrpc:
self.assertIs(api.as_view(), mock_jsonrpc)
def test_not_check_content_type(self):
client = self._get_test_client(JSONRPCAPI(check_content_type=False))
response = client.post(
'/',
data=self.REQUEST,
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf8'))
self.assertEqual(data['result'], '')
def test_check_content_type(self):
client = self._get_test_client(JSONRPCAPI(check_content_type=False))
response = client.post(
'/',
data=self.REQUEST,
content_type="application/x-www-form-urlencoded"
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf8'))
self.assertEqual(data['result'], '')
def test_empty_initial_dispatcher(self):
class SubDispatcher(type(api.dispatcher)):
pass
custom_dispatcher = SubDispatcher()
custom_api = JSONRPCAPI(custom_dispatcher)
self.assertEqual(type(custom_api.dispatcher), SubDispatcher)
self.assertEqual(id(custom_api.dispatcher), id(custom_dispatcher))
+39
View File
@@ -0,0 +1,39 @@
""" Test base JSON-RPC classes."""
import sys
from ..base import JSONRPCBaseRequest, JSONRPCBaseResponse
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest
class TestJSONRPCBaseRequest(unittest.TestCase):
""" Test JSONRPCBaseRequest functionality."""
def test_data(self):
request = JSONRPCBaseRequest()
self.assertEqual(request.data, {})
with self.assertRaises(ValueError):
request.data = []
with self.assertRaises(ValueError):
request.data = None
class TestJSONRPCBaseResponse(unittest.TestCase):
""" Test JSONRPCBaseResponse functionality."""
def test_data(self):
response = JSONRPCBaseResponse(result="")
self.assertEqual(response.data, {})
with self.assertRaises(ValueError):
response.data = []
with self.assertRaises(ValueError):
response.data = None
+34
View File
@@ -0,0 +1,34 @@
""" Exmples of usage with tests.
Tests in this file represent examples taken from JSON-RPC specification.
http://www.jsonrpc.org/specification#examples
"""
import sys
import json
from ..manager import JSONRPCResponseManager
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest
def isjsonequal(json1, json2):
return json.loads(json1) == json.loads(json2)
class TestJSONRPCExamples(unittest.TestCase):
def setUp(self):
self.dispatcher = {
"return_none": lambda: None,
}
def test_none_as_result(self):
req = '{"jsonrpc": "2.0", "method": "return_none", "id": 0}'
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(isjsonequal(
response.json,
'{"jsonrpc": "2.0", "result": null, "id": 0}'
))
+142
View File
@@ -0,0 +1,142 @@
from ..dispatcher import Dispatcher
import sys
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest
class Math:
def sum(self, a, b):
return a + b
def diff(self, a, b):
return a - b
class TestDispatcher(unittest.TestCase):
""" Test Dispatcher functionality."""
def test_getter(self):
d = Dispatcher()
with self.assertRaises(KeyError):
d["method"]
d["add"] = lambda *args: sum(args)
self.assertEqual(d["add"](1, 1), 2)
def test_in(self):
d = Dispatcher()
d["method"] = lambda: ""
self.assertIn("method", d)
def test_add_method(self):
d = Dispatcher()
@d.add_method
def add(x, y):
return x + y
self.assertIn("add", d)
self.assertEqual(d["add"](1, 1), 2)
def test_add_method_with_name(self):
d = Dispatcher()
@d.add_method(name="this.add")
def add(x, y):
return x + y
self.assertNotIn("add", d)
self.assertIn("this.add", d)
self.assertEqual(d["this.add"](1, 1), 2)
def test_add_class(self):
d = Dispatcher()
d.add_class(Math)
self.assertIn("math.sum", d)
self.assertIn("math.diff", d)
self.assertEqual(d["math.sum"](3, 8), 11)
self.assertEqual(d["math.diff"](6, 9), -3)
def test_add_object(self):
d = Dispatcher()
d.add_object(Math())
self.assertIn("math.sum", d)
self.assertIn("math.diff", d)
self.assertEqual(d["math.sum"](5, 2), 7)
self.assertEqual(d["math.diff"](15, 9), 6)
def test_add_dict(self):
d = Dispatcher()
d.add_dict({"sum": lambda *args: sum(args)}, "util")
self.assertIn("util.sum", d)
self.assertEqual(d["util.sum"](13, -2), 11)
def test_add_method_keep_function_definitions(self):
d = Dispatcher()
@d.add_method
def one(x):
return x
self.assertIsNotNone(one)
def test_del_method(self):
d = Dispatcher()
d["method"] = lambda: ""
self.assertIn("method", d)
del d["method"]
self.assertNotIn("method", d)
def test_to_dict(self):
d = Dispatcher()
def func():
return ""
d["method"] = func
self.assertEqual(dict(d), {"method": func})
def test_init_from_object_instance(self):
class Dummy():
def one(self):
pass
def two(self):
pass
dummy = Dummy()
d = Dispatcher(dummy)
self.assertIn("one", d)
self.assertIn("two", d)
self.assertNotIn("__class__", d)
def test_init_from_dictionary(self):
dummy = {
'one': lambda x: x,
'two': lambda x: x,
}
d = Dispatcher(dummy)
self.assertIn("one", d)
self.assertIn("two", d)
def test_dispatcher_representation(self):
d = Dispatcher()
self.assertEqual('{}', repr(d))
+206
View File
@@ -0,0 +1,206 @@
""" Exmples of usage with tests.
Tests in this file represent examples taken from JSON-RPC specification.
http://www.jsonrpc.org/specification#examples
"""
import sys
import json
from ..manager import JSONRPCResponseManager
from ..jsonrpc2 import JSONRPC20Request, JSONRPC20BatchRequest
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest
def isjsonequal(json1, json2):
return json.loads(json1) == json.loads(json2)
class TestJSONRPCExamples(unittest.TestCase):
def setUp(self):
self.dispatcher = {
"subtract": lambda a, b: a - b,
}
def test_rpc_call_with_positional_parameters(self):
req = '{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}' # noqa
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(isjsonequal(
response.json,
'{"jsonrpc": "2.0", "result": 19, "id": 1}'
))
req = '{"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2}' # noqa
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(isjsonequal(
response.json,
'{"jsonrpc": "2.0", "result": -19, "id": 2}'
))
def test_rpc_call_with_named_parameters(self):
def subtract(minuend=None, subtrahend=None):
return minuend - subtrahend
dispatcher = {
"subtract": subtract,
"sum": lambda *args: sum(args),
"get_data": lambda: ["hello", 5],
}
req = '{"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3}' # noqa
response = JSONRPCResponseManager.handle(req, dispatcher)
self.assertTrue(isjsonequal(
response.json,
'{"jsonrpc": "2.0", "result": 19, "id": 3}'
))
req = '{"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 4}' # noqa
response = JSONRPCResponseManager.handle(req, dispatcher)
self.assertTrue(isjsonequal(
response.json,
'{"jsonrpc": "2.0", "result": 19, "id": 4}',
))
def test_notification(self):
req = '{"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}'
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertEqual(response, None)
req = '{"jsonrpc": "2.0", "method": "foobar"}'
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertEqual(response, None)
def test_rpc_call_of_non_existent_method(self):
req = '{"jsonrpc": "2.0", "method": "foobar", "id": "1"}'
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(isjsonequal(
response.json,
'{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "1"}' # noqa
))
def test_rpc_call_with_invalid_json(self):
req = '{"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]'
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(isjsonequal(
response.json,
'{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}' # noqa
))
def test_rpc_call_with_invalid_request_object(self):
req = '{"jsonrpc": "2.0", "method": 1, "params": "bar"}'
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(isjsonequal(
response.json,
'{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}' # noqa
))
def test_rpc_call_batch_invalid_json(self):
req = """[
{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
{"jsonrpc": "2.0", "method"
]"""
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(isjsonequal(
response.json,
'{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}' # noqa
))
def test_rpc_call_with_an_empty_array(self):
req = '[]'
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(isjsonequal(
response.json,
'{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}' # noqa
))
def test_rpc_call_with_rpc_call_with_an_invalid_batch_but_not_empty(self):
req = '[1]'
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(isjsonequal(
response.json,
'{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}' # noqa
))
def test_rpc_call_with_invalid_batch(self):
req = '[1,2,3]'
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(
response,
json.loads("""[
{"jsonrpc": "2.0", "error": {"code": -32600,
"message": "Invalid Request"}, "id": null},
{"jsonrpc": "2.0", "error": {"code": -32600,
"message": "Invalid Request"}, "id": null},
{"jsonrpc": "2.0", "error": {"code": -32600,
"message": "Invalid Request"}, "id": null}
]""")
)
def test_rpc_call_batch(self):
req = """[
{"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"}
]"""
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(
response,
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", "result": ["hello", 5], "id": "9"}
]""")
)
def test_rpc_call_batch_all_notifications(self):
req = """[
{"jsonrpc": "2.0", "method": "notify_sum", "params": [1,2,4]},
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}
]"""
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertEqual(response, None)
def test_rpc_call_response_request(self):
req = '{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}' # noqa
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(isinstance(
response.request,
JSONRPC20Request
))
self.assertTrue(isjsonequal(
response.request.json,
req
))
def test_rpc_call_response_request_batch(self):
req = """[
{"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"},
{"jsonrpc": "2.0", "method": "foo.get",
"params": {"name": "myself"}, "id": "5"},
{"jsonrpc": "2.0", "method": "get_data", "id": "9"}
]"""
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(isinstance(
response.request,
JSONRPC20BatchRequest
))
self.assertTrue(isjsonequal(
response.request.json,
req
))
+1
View File
@@ -0,0 +1 @@
""" Tets base JSON-RPC structures."""
+429
View File
@@ -0,0 +1,429 @@
import json
import sys
from ..exceptions import JSONRPCInvalidRequestException
from ..jsonrpc1 import (
JSONRPC10Request,
JSONRPC10Response,
)
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest
class TestJSONRPC10Request(unittest.TestCase):
""" Test JSONRPC10Request functionality."""
def setUp(self):
self.request_params = {
"method": "add",
"params": [1, 2],
"_id": 1,
}
def test_correct_init(self):
""" Test object is created."""
JSONRPC10Request(**self.request_params)
def test_validation_incorrect_no_parameters(self):
with self.assertRaises(ValueError):
JSONRPC10Request()
def test_method_validation_str(self):
self.request_params.update({"method": "add"})
JSONRPC10Request(**self.request_params)
def test_method_validation_not_str(self):
self.request_params.update({"method": []})
with self.assertRaises(ValueError):
JSONRPC10Request(**self.request_params)
self.request_params.update({"method": {}})
with self.assertRaises(ValueError):
JSONRPC10Request(**self.request_params)
self.request_params.update({"method": None})
with self.assertRaises(ValueError):
JSONRPC10Request(**self.request_params)
def test_params_validation_list(self):
self.request_params.update({"params": []})
JSONRPC10Request(**self.request_params)
self.request_params.update({"params": [0]})
JSONRPC10Request(**self.request_params)
def test_params_validation_tuple(self):
self.request_params.update({"params": ()})
JSONRPC10Request(**self.request_params)
self.request_params.update({"params": tuple([0])})
JSONRPC10Request(**self.request_params)
def test_params_validation_dict(self):
self.request_params.update({"params": {}})
with self.assertRaises(ValueError):
JSONRPC10Request(**self.request_params)
self.request_params.update({"params": {"a": 0}})
with self.assertRaises(ValueError):
JSONRPC10Request(**self.request_params)
def test_params_validation_none(self):
self.request_params.update({"params": None})
with self.assertRaises(ValueError):
JSONRPC10Request(**self.request_params)
def test_params_validation_incorrect(self):
self.request_params.update({"params": "str"})
with self.assertRaises(ValueError):
JSONRPC10Request(**self.request_params)
def test_request_args(self):
self.assertEqual(JSONRPC10Request("add", []).args, ())
self.assertEqual(JSONRPC10Request("add", [1, 2]).args, (1, 2))
def test_id_validation_string(self):
self.request_params.update({"_id": "id"})
JSONRPC10Request(**self.request_params)
def test_id_validation_int(self):
self.request_params.update({"_id": 0})
JSONRPC10Request(**self.request_params)
def test_id_validation_null(self):
self.request_params.update({"_id": "null"})
JSONRPC10Request(**self.request_params)
def test_id_validation_none(self):
self.request_params.update({"_id": None})
JSONRPC10Request(**self.request_params)
def test_id_validation_float(self):
self.request_params.update({"_id": 0.1})
JSONRPC10Request(**self.request_params)
def test_id_validation_list_tuple(self):
self.request_params.update({"_id": []})
JSONRPC10Request(**self.request_params)
self.request_params.update({"_id": ()})
JSONRPC10Request(**self.request_params)
def test_id_validation_default_id_none(self):
del self.request_params["_id"]
JSONRPC10Request(**self.request_params)
def test_data_method_1(self):
r = JSONRPC10Request("add", [])
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"method": "add",
"params": [],
"id": None,
})
def test_data_method_2(self):
r = JSONRPC10Request(method="add", params=[])
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"method": "add",
"params": [],
"id": None,
})
def test_data_params_1(self):
r = JSONRPC10Request("add", params=[], _id=None)
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"method": "add",
"params": [],
"id": None,
})
def test_data_params_2(self):
r = JSONRPC10Request("add", ())
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"method": "add",
"params": [],
"id": None,
})
def test_data_params_3(self):
r = JSONRPC10Request("add", (1, 2))
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"method": "add",
"params": [1, 2],
"id": None,
})
def test_data_id_1(self):
r = JSONRPC10Request("add", [], _id="null")
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"method": "add",
"params": [],
"id": "null",
})
def test_data_id_1_notification(self):
r = JSONRPC10Request("add", [], _id="null", is_notification=True)
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"method": "add",
"params": [],
"id": None,
})
def test_data_id_2(self):
r = JSONRPC10Request("add", [], _id=None)
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"method": "add",
"params": [],
"id": None,
})
def test_data_id_2_notification(self):
r = JSONRPC10Request("add", [], _id=None, is_notification=True)
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"method": "add",
"params": [],
"id": None,
})
def test_data_id_3(self):
r = JSONRPC10Request("add", [], _id="id")
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"method": "add",
"params": [],
"id": "id",
})
def test_data_id_3_notification(self):
r = JSONRPC10Request("add", [], _id="id", is_notification=True)
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"method": "add",
"params": [],
"id": None,
})
def test_data_id_4(self):
r = JSONRPC10Request("add", [], _id=0)
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"method": "add",
"params": [],
"id": 0,
})
def test_data_id_4_notification(self):
r = JSONRPC10Request("add", [], _id=0, is_notification=True)
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"method": "add",
"params": [],
"id": None,
})
def test_is_notification(self):
r = JSONRPC10Request("add", [])
self.assertTrue(r.is_notification)
r = JSONRPC10Request("add", [], _id=None)
self.assertTrue(r.is_notification)
r = JSONRPC10Request("add", [], _id="null")
self.assertFalse(r.is_notification)
r = JSONRPC10Request("add", [], _id=0)
self.assertFalse(r.is_notification)
r = JSONRPC10Request("add", [], is_notification=True)
self.assertTrue(r.is_notification)
r = JSONRPC10Request("add", [], is_notification=True, _id=None)
self.assertTrue(r.is_notification)
r = JSONRPC10Request("add", [], is_notification=True, _id=0)
self.assertTrue(r.is_notification)
def test_set_unset_notification_keep_id(self):
r = JSONRPC10Request("add", [], is_notification=True, _id=0)
self.assertTrue(r.is_notification)
self.assertEqual(r.data["id"], None)
r.is_notification = False
self.assertFalse(r.is_notification)
self.assertEqual(r.data["id"], 0)
def test_error_if_notification_true_but_id_none(self):
r = JSONRPC10Request("add", [], is_notification=True, _id=None)
with self.assertRaises(ValueError):
r.is_notification = False
def test_from_json_invalid_request_method(self):
str_json = json.dumps({
"params": [1, 2],
"id": 0,
})
with self.assertRaises(JSONRPCInvalidRequestException):
JSONRPC10Request.from_json(str_json)
def test_from_json_invalid_request_params(self):
str_json = json.dumps({
"method": "add",
"id": 0,
})
with self.assertRaises(JSONRPCInvalidRequestException):
JSONRPC10Request.from_json(str_json)
def test_from_json_invalid_request_id(self):
str_json = json.dumps({
"method": "add",
"params": [1, 2],
})
with self.assertRaises(JSONRPCInvalidRequestException):
JSONRPC10Request.from_json(str_json)
def test_from_json_invalid_request_extra_data(self):
str_json = json.dumps({
"method": "add",
"params": [1, 2],
"id": 0,
"is_notification": True,
})
with self.assertRaises(JSONRPCInvalidRequestException):
JSONRPC10Request.from_json(str_json)
def test_from_json_request(self):
str_json = json.dumps({
"method": "add",
"params": [1, 2],
"id": 0,
})
request = JSONRPC10Request.from_json(str_json)
self.assertTrue(isinstance(request, JSONRPC10Request))
self.assertEqual(request.method, "add")
self.assertEqual(request.params, [1, 2])
self.assertEqual(request._id, 0)
self.assertFalse(request.is_notification)
def test_from_json_request_notification(self):
str_json = json.dumps({
"method": "add",
"params": [1, 2],
"id": None,
})
request = JSONRPC10Request.from_json(str_json)
self.assertTrue(isinstance(request, JSONRPC10Request))
self.assertEqual(request.method, "add")
self.assertEqual(request.params, [1, 2])
self.assertEqual(request._id, None)
self.assertTrue(request.is_notification)
def test_from_json_string_not_dict(self):
with self.assertRaises(ValueError):
JSONRPC10Request.from_json("[]")
with self.assertRaises(ValueError):
JSONRPC10Request.from_json("0")
def test_data_setter(self):
request = JSONRPC10Request(**self.request_params)
with self.assertRaises(ValueError):
request.data = []
with self.assertRaises(ValueError):
request.data = ""
with self.assertRaises(ValueError):
request.data = None
class TestJSONRPC10Response(unittest.TestCase):
""" Test JSONRPC10Response functionality."""
def setUp(self):
self.response_success_params = {
"result": "",
"error": None,
"_id": 1,
}
self.response_error_params = {
"result": None,
"error": {
"code": 1,
"message": "error",
},
"_id": 1,
}
def test_correct_init(self):
""" Test object is created."""
JSONRPC10Response(**self.response_success_params)
JSONRPC10Response(**self.response_error_params)
def test_validation_incorrect_no_parameters(self):
with self.assertRaises(ValueError):
JSONRPC10Response()
def test_validation_success_incorrect(self):
wrong_params = self.response_success_params
del wrong_params["_id"]
with self.assertRaises(ValueError):
JSONRPC10Response(**wrong_params)
def test_validation_error_incorrect(self):
wrong_params = self.response_error_params
del wrong_params["_id"]
with self.assertRaises(ValueError):
JSONRPC10Response(**wrong_params)
def _test_validation_incorrect_result_and_error(self):
# @todo: remove
# It is OK because result is an mepty string, it is still result
with self.assertRaises(ValueError):
JSONRPC10Response(result="", error="", _id=0)
response = JSONRPC10Response(error="", _id=0)
with self.assertRaises(ValueError):
response.result = ""
def test_data(self):
r = JSONRPC10Response(result="", _id=0)
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"result": "",
"id": 0,
})
def test_data_setter(self):
response = JSONRPC10Response(**self.response_success_params)
with self.assertRaises(ValueError):
response.data = []
with self.assertRaises(ValueError):
response.data = ""
with self.assertRaises(ValueError):
response.data = None
def test_validation_id(self):
response = JSONRPC10Response(**self.response_success_params)
self.assertEqual(response._id, self.response_success_params["_id"])
+728
View File
@@ -0,0 +1,728 @@
import json
import sys
from ..exceptions import JSONRPCInvalidRequestException
from ..jsonrpc2 import (
JSONRPC20Request,
JSONRPC20BatchRequest,
JSONRPC20Response,
JSONRPC20BatchResponse,
)
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest
class TestJSONRPC20Request(unittest.TestCase):
""" Test JSONRPC20Request functionality."""
def setUp(self):
self.request_params = {
"method": "add",
"params": [1, 2],
"_id": 1,
}
def test_correct_init(self):
""" Test object is created."""
JSONRPC20Request(**self.request_params)
def test_validation_incorrect_no_parameters(self):
with self.assertRaises(ValueError):
JSONRPC20Request()
def test_method_validation_str(self):
self.request_params.update({"method": "add"})
JSONRPC20Request(**self.request_params)
def test_method_validation_not_str(self):
self.request_params.update({"method": []})
with self.assertRaises(ValueError):
JSONRPC20Request(**self.request_params)
self.request_params.update({"method": {}})
with self.assertRaises(ValueError):
JSONRPC20Request(**self.request_params)
def test_method_validation_str_rpc_prefix(self):
""" Test method SHOULD NOT starts with rpc. """
self.request_params.update({"method": "rpc."})
with self.assertRaises(ValueError):
JSONRPC20Request(**self.request_params)
self.request_params.update({"method": "rpc.test"})
with self.assertRaises(ValueError):
JSONRPC20Request(**self.request_params)
self.request_params.update({"method": "rpccorrect"})
JSONRPC20Request(**self.request_params)
self.request_params.update({"method": "rpc"})
JSONRPC20Request(**self.request_params)
def test_params_validation_list(self):
self.request_params.update({"params": []})
JSONRPC20Request(**self.request_params)
self.request_params.update({"params": [0]})
JSONRPC20Request(**self.request_params)
def test_params_validation_tuple(self):
self.request_params.update({"params": ()})
JSONRPC20Request(**self.request_params)
self.request_params.update({"params": tuple([0])})
JSONRPC20Request(**self.request_params)
def test_params_validation_dict(self):
self.request_params.update({"params": {}})
JSONRPC20Request(**self.request_params)
self.request_params.update({"params": {"a": 0}})
JSONRPC20Request(**self.request_params)
def test_params_validation_none(self):
self.request_params.update({"params": None})
JSONRPC20Request(**self.request_params)
def test_params_validation_incorrect(self):
self.request_params.update({"params": "str"})
with self.assertRaises(ValueError):
JSONRPC20Request(**self.request_params)
def test_request_args(self):
self.assertEqual(JSONRPC20Request("add").args, ())
self.assertEqual(JSONRPC20Request("add", []).args, ())
self.assertEqual(JSONRPC20Request("add", {"a": 1}).args, ())
self.assertEqual(JSONRPC20Request("add", [1, 2]).args, (1, 2))
def test_request_kwargs(self):
self.assertEqual(JSONRPC20Request("add").kwargs, {})
self.assertEqual(JSONRPC20Request("add", [1, 2]).kwargs, {})
self.assertEqual(JSONRPC20Request("add", {}).kwargs, {})
self.assertEqual(JSONRPC20Request("add", {"a": 1}).kwargs, {"a": 1})
def test_id_validation_string(self):
self.request_params.update({"_id": "id"})
JSONRPC20Request(**self.request_params)
def test_id_validation_int(self):
self.request_params.update({"_id": 0})
JSONRPC20Request(**self.request_params)
def test_id_validation_null(self):
self.request_params.update({"_id": "null"})
JSONRPC20Request(**self.request_params)
def test_id_validation_none(self):
self.request_params.update({"_id": None})
JSONRPC20Request(**self.request_params)
def test_id_validation_float(self):
self.request_params.update({"_id": 0.1})
with self.assertRaises(ValueError):
JSONRPC20Request(**self.request_params)
def test_id_validation_incorrect(self):
self.request_params.update({"_id": []})
with self.assertRaises(ValueError):
JSONRPC20Request(**self.request_params)
self.request_params.update({"_id": ()})
with self.assertRaises(ValueError):
JSONRPC20Request(**self.request_params)
def test_data_method_1(self):
r = JSONRPC20Request("add")
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
"id": None,
})
def test_data_method_2(self):
r = JSONRPC20Request(method="add")
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
"id": None,
})
def test_data_method_3(self):
r = JSONRPC20Request("add", None)
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
"id": None,
})
def test_data_params_1(self):
r = JSONRPC20Request("add", params=None, _id=None)
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
"id": None,
})
def test_data_params_2(self):
r = JSONRPC20Request("add", [])
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
"params": [],
"id": None,
})
def test_data_params_3(self):
r = JSONRPC20Request("add", ())
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
"params": [],
"id": None,
})
def test_data_params_4(self):
r = JSONRPC20Request("add", (1, 2))
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
"params": [1, 2],
"id": None,
})
def test_data_params_5(self):
r = JSONRPC20Request("add", {"a": 0})
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
"params": {"a": 0},
"id": None,
})
def test_data_id_1(self):
r = JSONRPC20Request("add", _id="null")
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
"id": "null",
})
def test_data_id_1_notification(self):
r = JSONRPC20Request("add", _id="null", is_notification=True)
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
})
def test_data_id_2(self):
r = JSONRPC20Request("add", _id=None)
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
"id": None,
})
def test_data_id_2_notification(self):
r = JSONRPC20Request("add", _id=None, is_notification=True)
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
})
def test_data_id_3(self):
r = JSONRPC20Request("add", _id="id")
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
"id": "id",
})
def test_data_id_3_notification(self):
r = JSONRPC20Request("add", _id="id", is_notification=True)
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
})
def test_data_id_4(self):
r = JSONRPC20Request("add", _id=0)
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
"id": 0,
})
def test_data_id_4_notification(self):
r = JSONRPC20Request("add", _id=0, is_notification=True)
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"method": "add",
})
def test_is_notification(self):
r = JSONRPC20Request("add")
self.assertFalse(r.is_notification)
r = JSONRPC20Request("add", _id=None)
self.assertFalse(r.is_notification)
r = JSONRPC20Request("add", _id="null")
self.assertFalse(r.is_notification)
r = JSONRPC20Request("add", _id=0)
self.assertFalse(r.is_notification)
r = JSONRPC20Request("add", is_notification=True)
self.assertTrue(r.is_notification)
r = JSONRPC20Request("add", is_notification=True, _id=None)
self.assertTrue(r.is_notification)
self.assertNotIn("id", r.data)
r = JSONRPC20Request("add", is_notification=True, _id=0)
self.assertTrue(r.is_notification)
self.assertNotIn("id", r.data)
def test_set_unset_notification_keep_id(self):
r = JSONRPC20Request("add", is_notification=True, _id=0)
self.assertTrue(r.is_notification)
self.assertFalse("id" in r.data)
r.is_notification = False
self.assertFalse(r.is_notification)
self.assertTrue("id" in r.data)
self.assertEqual(r.data["id"], 0)
def test_serialize_method_1(self):
r = JSONRPC20Request("add")
self.assertTrue({
"jsonrpc": "2.0",
"method": "add",
"id": None,
}, json.loads(r.json))
def test_serialize_method_2(self):
r = JSONRPC20Request(method="add")
self.assertTrue({
"jsonrpc": "2.0",
"method": "add",
"id": None,
}, json.loads(r.json))
def test_serialize_method_3(self):
r = JSONRPC20Request("add", None)
self.assertTrue({
"jsonrpc": "2.0",
"method": "add",
"id": None,
}, json.loads(r.json))
def test_serialize_params_1(self):
r = JSONRPC20Request("add", params=None, _id=None)
self.assertTrue({
"jsonrpc": "2.0",
"method": "add",
"id": None,
}, json.loads(r.json))
def test_serialize_params_2(self):
r = JSONRPC20Request("add", [])
self.assertTrue({
"jsonrpc": "2.0",
"method": "add",
"params": [],
"id": None,
}, json.loads(r.json))
def test_serialize_params_3(self):
r = JSONRPC20Request("add", ())
self.assertTrue({
"jsonrpc": "2.0",
"method": "add",
"params": [],
"id": None,
}, json.loads(r.json))
def test_serialize_params_4(self):
r = JSONRPC20Request("add", (1, 2))
self.assertTrue({
"jsonrpc": "2.0",
"method": "add",
"params": [1, 2],
"id": None,
}, json.loads(r.json))
def test_serialize_params_5(self):
r = JSONRPC20Request("add", {"a": 0})
self.assertTrue({
"jsonrpc": "2.0",
"method": "add",
"params": {"a": 0},
"id": None,
}, json.loads(r.json))
def test_serialize_id_1(self):
r = JSONRPC20Request("add", _id="null")
self.assertTrue({
"jsonrpc": "2.0",
"method": "add",
"id": "null",
}, json.loads(r.json))
def test_serialize_id_2(self):
r = JSONRPC20Request("add", _id=None)
self.assertTrue({
"jsonrpc": "2.0",
"method": "add",
"id": None,
}, json.loads(r.json))
def test_serialize_id_3(self):
r = JSONRPC20Request("add", _id="id")
self.assertTrue({
"jsonrpc": "2.0",
"method": "add",
"id": "id",
}, json.loads(r.json))
def test_serialize_id_4(self):
r = JSONRPC20Request("add", _id=0)
self.assertTrue({
"jsonrpc": "2.0",
"method": "add",
"id": 0,
}, json.loads(r.json))
def test_from_json_request_no_id(self):
str_json = json.dumps({
"method": "add",
"params": [1, 2],
"jsonrpc": "2.0",
})
request = JSONRPC20Request.from_json(str_json)
self.assertTrue(isinstance(request, JSONRPC20Request))
self.assertEqual(request.method, "add")
self.assertEqual(request.params, [1, 2])
self.assertEqual(request._id, None)
self.assertTrue(request.is_notification)
def test_from_json_request_no_params(self):
str_json = json.dumps({
"method": "add",
"jsonrpc": "2.0",
})
request = JSONRPC20Request.from_json(str_json)
self.assertTrue(isinstance(request, JSONRPC20Request))
self.assertEqual(request.method, "add")
self.assertEqual(request.params, None)
self.assertEqual(request._id, None)
self.assertTrue(request.is_notification)
def test_from_json_request_null_id(self):
str_json = json.dumps({
"method": "add",
"jsonrpc": "2.0",
"id": None,
})
request = JSONRPC20Request.from_json(str_json)
self.assertTrue(isinstance(request, JSONRPC20Request))
self.assertEqual(request.method, "add")
self.assertEqual(request.params, None)
self.assertEqual(request._id, None)
self.assertFalse(request.is_notification)
def test_from_json_request(self):
str_json = json.dumps({
"method": "add",
"params": [0, 1],
"jsonrpc": "2.0",
"id": "id",
})
request = JSONRPC20Request.from_json(str_json)
self.assertTrue(isinstance(request, JSONRPC20Request))
self.assertEqual(request.method, "add")
self.assertEqual(request.params, [0, 1])
self.assertEqual(request._id, "id")
self.assertFalse(request.is_notification)
def test_from_json_invalid_request_jsonrpc(self):
str_json = json.dumps({
"method": "add",
})
with self.assertRaises(JSONRPCInvalidRequestException):
JSONRPC20Request.from_json(str_json)
def test_from_json_invalid_request_method(self):
str_json = json.dumps({
"jsonrpc": "2.0",
})
with self.assertRaises(JSONRPCInvalidRequestException):
JSONRPC20Request.from_json(str_json)
def test_from_json_invalid_request_extra_data(self):
str_json = json.dumps({
"jsonrpc": "2.0",
"method": "add",
"is_notification": True,
})
with self.assertRaises(JSONRPCInvalidRequestException):
JSONRPC20Request.from_json(str_json)
def test_data_setter(self):
request = JSONRPC20Request(**self.request_params)
with self.assertRaises(ValueError):
request.data = []
with self.assertRaises(ValueError):
request.data = ""
with self.assertRaises(ValueError):
request.data = None
class TestJSONRPC20BatchRequest(unittest.TestCase):
""" Test JSONRPC20BatchRequest functionality."""
def test_batch_request(self):
request = JSONRPC20BatchRequest(
JSONRPC20Request("devide", {"num": 1, "denom": 2}, _id=1),
JSONRPC20Request("devide", {"num": 3, "denom": 2}, _id=2),
)
self.assertEqual(json.loads(request.json), [
{"method": "devide", "params": {"num": 1, "denom": 2}, "id": 1,
"jsonrpc": "2.0"},
{"method": "devide", "params": {"num": 3, "denom": 2}, "id": 2,
"jsonrpc": "2.0"},
])
def test_from_json_batch(self):
str_json = json.dumps([
{"method": "add", "params": [1, 2], "jsonrpc": "2.0"},
{"method": "mul", "params": [1, 2], "jsonrpc": "2.0"},
])
requests = JSONRPC20BatchRequest.from_json(str_json)
self.assertTrue(isinstance(requests, JSONRPC20BatchRequest))
for r in requests:
self.assertTrue(isinstance(r, JSONRPC20Request))
self.assertTrue(r.method in ["add", "mul"])
self.assertEqual(r.params, [1, 2])
self.assertEqual(r._id, None)
self.assertTrue(r.is_notification)
def test_from_json_batch_one(self):
str_json = json.dumps([
{"method": "add", "params": [1, 2], "jsonrpc": "2.0", "id": None},
])
requests = JSONRPC20Request.from_json(str_json)
self.assertTrue(isinstance(requests, JSONRPC20BatchRequest))
requests = list(requests)
self.assertEqual(len(requests), 1)
r = requests[0]
self.assertTrue(isinstance(r, JSONRPC20Request))
self.assertEqual(r.method, "add")
self.assertEqual(r.params, [1, 2])
self.assertEqual(r._id, None)
self.assertFalse(r.is_notification)
def test_response_iterator(self):
requests = JSONRPC20BatchRequest(
JSONRPC20Request("devide", {"num": 1, "denom": 2}, _id=1),
JSONRPC20Request("devide", {"num": 3, "denom": 2}, _id=2),
)
for request in requests:
self.assertTrue(isinstance(request, JSONRPC20Request))
self.assertEqual(request.method, "devide")
class TestJSONRPC20Response(unittest.TestCase):
""" Test JSONRPC20Response functionality."""
def setUp(self):
self.response_success_params = {
"result": "",
"_id": 1,
}
self.response_error_params = {
"error": {
"code": 1,
"message": "error",
},
"_id": 1,
}
def test_correct_init(self):
""" Test object is created."""
JSONRPC20Response(**self.response_success_params)
def test_validation_incorrect_no_parameters(self):
with self.assertRaises(ValueError):
JSONRPC20Response()
def test_validation_incorrect_result_and_error(self):
response = JSONRPC20Response(error={"code": 1, "message": ""})
with self.assertRaises(ValueError):
response.result = ""
def test_validation_error_correct(self):
JSONRPC20Response(**self.response_error_params)
def test_validation_error_incorrect(self):
self.response_error_params["error"].update({"code": "str"})
with self.assertRaises(ValueError):
JSONRPC20Response(**self.response_error_params)
def test_validation_error_incorrect_no_code(self):
del self.response_error_params["error"]["code"]
with self.assertRaises(ValueError):
JSONRPC20Response(**self.response_error_params)
def test_validation_error_incorrect_no_message(self):
del self.response_error_params["error"]["message"]
with self.assertRaises(ValueError):
JSONRPC20Response(**self.response_error_params)
def test_validation_error_incorrect_message_not_str(self):
self.response_error_params["error"].update({"message": 0})
with self.assertRaises(ValueError):
JSONRPC20Response(**self.response_error_params)
def test_validation_id(self):
response = JSONRPC20Response(**self.response_success_params)
self.assertEqual(response._id, self.response_success_params["_id"])
def test_validation_id_incorrect_type(self):
response = JSONRPC20Response(**self.response_success_params)
with self.assertRaises(ValueError):
response._id = []
with self.assertRaises(ValueError):
response._id = {}
with self.assertRaises(ValueError):
response._id = 0.1
def test_data_result(self):
r = JSONRPC20Response(result="")
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"result": "",
"id": None,
})
def test_data_result_id_none(self):
r = JSONRPC20Response(result="", _id=None)
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"result": "",
"id": None,
})
def test_data_result_id(self):
r = JSONRPC20Response(result="", _id=0)
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"result": "",
"id": 0,
})
def test_data_error(self):
r = JSONRPC20Response(error={"code": 0, "message": ""})
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"error": {
"code": 0,
"message": "",
},
"id": None,
})
def test_data_error_id_none(self):
r = JSONRPC20Response(error={"code": 0, "message": ""}, _id=None)
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"error": {
"code": 0,
"message": "",
},
"id": None,
})
def test_data_error_id(self):
r = JSONRPC20Response(error={"code": 0, "message": ""}, _id=0)
self.assertEqual(json.loads(r.json), r.data)
self.assertEqual(r.data, {
"jsonrpc": "2.0",
"error": {
"code": 0,
"message": "",
},
"id": 0,
})
def test_data_setter(self):
response = JSONRPC20Response(**self.response_success_params)
with self.assertRaises(ValueError):
response.data = []
with self.assertRaises(ValueError):
response.data = ""
with self.assertRaises(ValueError):
response.data = None
class TestJSONRPC20BatchResponse(unittest.TestCase):
""" Test JSONRPC20BatchResponse functionality."""
def test_batch_response(self):
response = JSONRPC20BatchResponse(
JSONRPC20Response(result="result", _id=1),
JSONRPC20Response(error={"code": 0, "message": ""}, _id=2),
)
self.assertEqual(json.loads(response.json), [
{"result": "result", "id": 1, "jsonrpc": "2.0"},
{"error": {"code": 0, "message": ""}, "id": 2, "jsonrpc": "2.0"},
])
def test_response_iterator(self):
responses = JSONRPC20BatchResponse(
JSONRPC20Response(result="result", _id=1),
JSONRPC20Response(result="result", _id=2),
)
for response in responses:
self.assertTrue(isinstance(response, JSONRPC20Response))
self.assertEqual(response.result, "result")
def test_batch_response_data(self):
response = JSONRPC20BatchResponse(
JSONRPC20Response(result="result", _id=1),
JSONRPC20Response(result="result", _id=2),
JSONRPC20Response(result="result"),
)
self.assertEqual(response.data, [
{"id": 1, "jsonrpc": "2.0", "result": "result"},
{"id": 2, "jsonrpc": "2.0", "result": "result"},
{"id": None, "jsonrpc": "2.0", "result": "result"},
])
@@ -0,0 +1,150 @@
import json
import sys
from ..exceptions import (
JSONRPCError,
JSONRPCInternalError,
JSONRPCInvalidParams,
JSONRPCInvalidRequest,
JSONRPCMethodNotFound,
JSONRPCParseError,
JSONRPCServerError,
JSONRPCDispatchException,
)
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest
class TestJSONRPCError(unittest.TestCase):
def setUp(self):
self.error_params = {
"code": 0,
"message": "",
}
def test_correct_init(self):
""" Test object is created."""
JSONRPCError(**self.error_params)
def test_validation_incorrect_no_parameters(self):
with self.assertRaises(ValueError):
JSONRPCError()
def test_code_validation_int(self):
self.error_params.update({"code": 32000})
JSONRPCError(**self.error_params)
def test_code_validation_no_code(self):
del self.error_params["code"]
with self.assertRaises(ValueError):
JSONRPCError(**self.error_params)
def test_code_validation_str(self):
self.error_params.update({"code": "0"})
with self.assertRaises(ValueError):
JSONRPCError(**self.error_params)
def test_message_validation_str(self):
self.error_params.update({"message": ""})
JSONRPCError(**self.error_params)
def test_message_validation_none(self):
del self.error_params["message"]
with self.assertRaises(ValueError):
JSONRPCError(**self.error_params)
def test_message_validation_int(self):
self.error_params.update({"message": 0})
with self.assertRaises(ValueError):
JSONRPCError(**self.error_params)
def test_data_validation_none(self):
self.error_params.update({"data": None})
JSONRPCError(**self.error_params)
def test_data_validation(self):
self.error_params.update({"data": {}})
JSONRPCError(**self.error_params)
self.error_params.update({"data": ""})
JSONRPCError(**self.error_params)
def test_json(self):
error = JSONRPCError(**self.error_params)
self.assertEqual(
json.loads(error.json),
self.error_params,
)
def test_from_json(self):
str_json = json.dumps({
"code": 0,
"message": "",
"data": {},
})
request = JSONRPCError.from_json(str_json)
self.assertTrue(isinstance(request, JSONRPCError))
self.assertEqual(request.code, 0)
self.assertEqual(request.message, "")
self.assertEqual(request.data, {})
class TestJSONRPCParseError(unittest.TestCase):
def test_code_message(self):
error = JSONRPCParseError()
self.assertEqual(error.code, -32700)
self.assertEqual(error.message, "Parse error")
self.assertEqual(error.data, None)
class TestJSONRPCServerError(unittest.TestCase):
def test_code_message(self):
error = JSONRPCServerError()
self.assertEqual(error.code, -32000)
self.assertEqual(error.message, "Server error")
self.assertEqual(error.data, None)
class TestJSONRPCInternalError(unittest.TestCase):
def test_code_message(self):
error = JSONRPCInternalError()
self.assertEqual(error.code, -32603)
self.assertEqual(error.message, "Internal error")
self.assertEqual(error.data, None)
class TestJSONRPCInvalidParams(unittest.TestCase):
def test_code_message(self):
error = JSONRPCInvalidParams()
self.assertEqual(error.code, -32602)
self.assertEqual(error.message, "Invalid params")
self.assertEqual(error.data, None)
class TestJSONRPCInvalidRequest(unittest.TestCase):
def test_code_message(self):
error = JSONRPCInvalidRequest()
self.assertEqual(error.code, -32600)
self.assertEqual(error.message, "Invalid Request")
self.assertEqual(error.data, None)
class TestJSONRPCMethodNotFound(unittest.TestCase):
def test_code_message(self):
error = JSONRPCMethodNotFound()
self.assertEqual(error.code, -32601)
self.assertEqual(error.message, "Method not found")
self.assertEqual(error.data, None)
class TestJSONRPCDispatchException(unittest.TestCase):
def test_code_message(self):
error = JSONRPCDispatchException(message="message",
code=400, data={"param": 1})
self.assertEqual(error.error.code, 400)
self.assertEqual(error.error.message, "message")
self.assertEqual(error.error.data, {"param": 1})
+175
View File
@@ -0,0 +1,175 @@
import sys
from ..manager import JSONRPCResponseManager
from ..jsonrpc2 import (
JSONRPC20BatchRequest,
JSONRPC20BatchResponse,
JSONRPC20Request,
JSONRPC20Response,
)
from ..jsonrpc1 import JSONRPC10Request, JSONRPC10Response
from ..exceptions import JSONRPCDispatchException
if sys.version_info < (3, 3):
from mock import MagicMock
else:
from unittest.mock import MagicMock
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest
class TestJSONRPCResponseManager(unittest.TestCase):
def setUp(self):
def raise_(e):
raise e
self.long_time_method = MagicMock()
self.dispatcher = {
"add": sum,
"multiply": lambda a, b: a * b,
"list_len": len,
"101_base": lambda **kwargs: int("101", **kwargs),
"error": lambda: raise_(KeyError("error_explanation")),
"type_error": lambda: raise_(TypeError("TypeError inside method")),
"long_time_method": self.long_time_method,
"dispatch_error": lambda x: raise_(
JSONRPCDispatchException(code=4000, message="error",
data={"param": 1})),
}
def test_dispatch_error(self):
request = JSONRPC20Request("dispatch_error", ["test"], _id=0)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC20Response))
self.assertEqual(response.error["message"], "error")
self.assertEqual(response.error["code"], 4000)
self.assertEqual(response.error["data"], {"param": 1})
def test_returned_type_response(self):
request = JSONRPC20Request("add", [[]], _id=0)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC20Response))
def test_returned_type_butch_response(self):
request = JSONRPC20BatchRequest(
JSONRPC20Request("add", [[]], _id=0))
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC20BatchResponse))
def test_returned_type_response_rpc10(self):
request = JSONRPC10Request("add", [[]], _id=0)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC10Response))
def test_parse_error(self):
req = '{"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]'
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC20Response))
self.assertEqual(response.error["message"], "Parse error")
self.assertEqual(response.error["code"], -32700)
def test_invalid_request(self):
req = '{"jsonrpc": "2.0", "method": 1, "params": "bar"}'
response = JSONRPCResponseManager.handle(req, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC20Response))
self.assertEqual(response.error["message"], "Invalid Request")
self.assertEqual(response.error["code"], -32600)
def test_method_not_found(self):
request = JSONRPC20Request("does_not_exist", [[]], _id=0)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC20Response))
self.assertEqual(response.error["message"], "Method not found")
self.assertEqual(response.error["code"], -32601)
def test_invalid_params(self):
request = JSONRPC20Request("add", {"a": 0}, _id=0)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC20Response))
self.assertEqual(response.error["message"], "Invalid params")
self.assertEqual(response.error["code"], -32602)
self.assertIn(response.error["data"]["message"], [
'sum() takes no keyword arguments',
"sum() got an unexpected keyword argument 'a'",
])
def test_invalid_params_custom_function(self):
request = JSONRPC20Request("multiply", [0], _id=0)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC20Response))
self.assertEqual(response.error["message"], "Invalid params")
self.assertEqual(response.error["code"], -32602)
request = JSONRPC20Request("multiply", [0, 1, 2], _id=0)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC20Response))
self.assertEqual(response.error["message"], "Invalid params")
self.assertEqual(response.error["code"], -32602)
request = JSONRPC20Request("multiply", {"a": 1}, _id=0)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC20Response))
self.assertEqual(response.error["message"], "Invalid params")
self.assertEqual(response.error["code"], -32602)
request = JSONRPC20Request("multiply", {"a": 1, "b": 2, "c": 3}, _id=0)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC20Response))
self.assertEqual(response.error["message"], "Invalid params")
self.assertEqual(response.error["code"], -32602)
def test_server_error(self):
request = JSONRPC20Request("error", _id=0)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC20Response))
self.assertEqual(response.error["message"], "Server error")
self.assertEqual(response.error["code"], -32000)
self.assertEqual(response.error["data"]['type'], "KeyError")
self.assertEqual(
response.error["data"]['args'], ('error_explanation',))
self.assertEqual(
response.error["data"]['message'], "'error_explanation'")
def test_notification_calls_method(self):
request = JSONRPC20Request("long_time_method", is_notification=True)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertEqual(response, None)
self.long_time_method.assert_called_once_with()
def test_notification_does_not_return_error_does_not_exist(self):
request = JSONRPC20Request("does_not_exist", is_notification=True)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertEqual(response, None)
def test_notification_does_not_return_error_invalid_params(self):
request = JSONRPC20Request("add", {"a": 0}, is_notification=True)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertEqual(response, None)
def test_notification_does_not_return_error(self):
request = JSONRPC20Request("error", is_notification=True)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertEqual(response, None)
def test_type_error_inside_method(self):
request = JSONRPC20Request("type_error", _id=0)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC20Response))
self.assertEqual(response.error["message"], "Server error")
self.assertEqual(response.error["code"], -32000)
self.assertEqual(response.error["data"]['type'], "TypeError")
self.assertEqual(
response.error["data"]['args'], ('TypeError inside method',))
self.assertEqual(
response.error["data"]['message'], 'TypeError inside method')
def test_invalid_params_before_dispatcher_error(self):
request = JSONRPC20Request(
"dispatch_error", ["invalid", "params"], _id=0)
response = JSONRPCResponseManager.handle(request.json, self.dispatcher)
self.assertTrue(isinstance(response, JSONRPC20Response))
self.assertEqual(response.error["message"], "Invalid params")
self.assertEqual(response.error["code"], -32602)
+28
View File
@@ -0,0 +1,28 @@
from ..manager import JSONRPCResponseManager
import sys
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest
class TestJSONRPCResponseManager(unittest.TestCase):
@unittest.skipIf(sys.version_info < (3, 5), "Test Py3.5+ functionality")
def test_typeerror_with_annotations(self):
"""If a function has Python3 annotations and is called with improper
arguments, make sure the framework doesn't fail with inspect.getargspec
"""
from .py35_utils import distance
dispatcher = {
"distance": distance,
}
req = '{"jsonrpc": "2.0", "method": "distance", "params": [], "id": 1}'
result = JSONRPCResponseManager.handle(req, dispatcher)
# Make sure this returns JSONRPCInvalidParams rather than raising
# UnboundLocalError
self.assertEqual(result.error['code'], -32602)
+130
View File
@@ -0,0 +1,130 @@
""" Test utility functionality."""
from ..utils import JSONSerializable, DatetimeDecimalEncoder, is_invalid_params
import datetime
import decimal
import json
import sys
if sys.version_info < (3, 3):
from mock import patch
else:
from unittest.mock import patch
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest
class TestJSONSerializable(unittest.TestCase):
""" Test JSONSerializable functionality."""
def setUp(self):
class A(JSONSerializable):
@property
def json(self):
pass
self._class = A
def test_abstract_class(self):
with self.assertRaises(TypeError):
JSONSerializable()
self._class()
def test_definse_serialize_deserialize(self):
""" Test classmethods of inherited class."""
self.assertEqual(self._class.serialize({}), "{}")
self.assertEqual(self._class.deserialize("{}"), {})
def test_from_json(self):
self.assertTrue(isinstance(self._class.from_json('{}'), self._class))
def test_from_json_incorrect(self):
with self.assertRaises(ValueError):
self._class.from_json('[]')
class TestDatetimeDecimalEncoder(unittest.TestCase):
""" Test DatetimeDecimalEncoder functionality."""
def test_date_encoder(self):
obj = datetime.date.today()
with self.assertRaises(TypeError):
json.dumps(obj)
self.assertEqual(
json.dumps(obj, cls=DatetimeDecimalEncoder),
'"{0}"'.format(obj.isoformat()),
)
def test_datetime_encoder(self):
obj = datetime.datetime.now()
with self.assertRaises(TypeError):
json.dumps(obj)
self.assertEqual(
json.dumps(obj, cls=DatetimeDecimalEncoder),
'"{0}"'.format(obj.isoformat()),
)
def test_decimal_encoder(self):
obj = decimal.Decimal('0.1')
with self.assertRaises(TypeError):
json.dumps(obj)
result = json.dumps(obj, cls=DatetimeDecimalEncoder)
self.assertTrue(isinstance(result, str))
self.assertEqual(float(result), float(0.1))
def test_default(self):
encoder = DatetimeDecimalEncoder()
with patch.object(json.JSONEncoder, 'default') as json_default:
encoder.default("")
self.assertEqual(json_default.call_count, 1)
class TestUtils(unittest.TestCase):
""" Test utils functions."""
def test_is_invalid_params_builtin(self):
self.assertTrue(is_invalid_params(sum, 0, 0))
# NOTE: builtin functions could not be recognized by inspect.isfunction
# It would raise TypeError if parameters are incorrect already.
# self.assertFalse(is_invalid_params(sum, [0, 0])) # <- fails
def test_is_invalid_params_args(self):
self.assertTrue(is_invalid_params(lambda a, b: None, 0))
self.assertTrue(is_invalid_params(lambda a, b: None, 0, 1, 2))
def test_is_invalid_params_kwargs(self):
self.assertTrue(is_invalid_params(lambda a: None, **{}))
self.assertTrue(is_invalid_params(lambda a: None, **{"a": 0, "b": 1}))
def test_invalid_params_correct(self):
self.assertFalse(is_invalid_params(lambda: None))
self.assertFalse(is_invalid_params(lambda a: None, 0))
self.assertFalse(is_invalid_params(lambda a, b=0: None, 0))
self.assertFalse(is_invalid_params(lambda a, b=0: None, 0, 0))
def test_is_invalid_params_mixed(self):
self.assertFalse(is_invalid_params(lambda a, b: None, 0, **{"b": 1}))
self.assertFalse(is_invalid_params(
lambda a, b, c=0: None, 0, **{"b": 1}))
def test_is_invalid_params_py2(self):
with patch('jsonrpc.utils.sys') as mock_sys:
mock_sys.version_info = (2, 7)
with patch('jsonrpc.utils.is_invalid_params_py2') as mock_func:
is_invalid_params(lambda a: None, 0)
assert mock_func.call_count == 1
+135
View File
@@ -0,0 +1,135 @@
""" Utility functions for package."""
from abc import ABCMeta, abstractmethod
import datetime
import decimal
import inspect
import json
import sys
from . import six
class JSONSerializable(six.with_metaclass(ABCMeta, object)):
""" Common functionality for json serializable objects."""
serialize = staticmethod(json.dumps)
deserialize = staticmethod(json.loads)
@abstractmethod
def json(self):
raise NotImplementedError()
@classmethod
def from_json(cls, json_str):
data = cls.deserialize(json_str)
if not isinstance(data, dict):
raise ValueError("data should be dict")
return cls(**data)
class DatetimeDecimalEncoder(json.JSONEncoder):
""" Encoder for datetime and decimal serialization.
Usage: json.dumps(object, cls=DatetimeDecimalEncoder)
NOTE: _iterencode does not work
"""
def default(self, o):
""" Encode JSON.
:return str: A JSON encoded string
"""
if isinstance(o, decimal.Decimal):
return float(o)
if isinstance(o, (datetime.datetime, datetime.date)):
return o.isoformat()
return json.JSONEncoder.default(self, o)
def is_invalid_params_py2(func, *args, **kwargs):
""" Check, whether function 'func' accepts parameters 'args', 'kwargs'.
NOTE: Method is called after funct(*args, **kwargs) generated TypeError,
it is aimed to destinguish TypeError because of invalid parameters from
TypeError from inside the function.
.. versionadded: 1.9.0
"""
funcargs, varargs, varkwargs, defaults = inspect.getargspec(func)
unexpected = set(kwargs.keys()) - set(funcargs)
if len(unexpected) > 0:
return True
params = [funcarg for funcarg in funcargs if funcarg not in kwargs]
funcargs_required = funcargs[:-len(defaults)] \
if defaults is not None \
else funcargs
params_required = [
funcarg for funcarg in funcargs_required
if funcarg not in kwargs
]
return not (len(params_required) <= len(args) <= len(params))
def is_invalid_params_py3(func, *args, **kwargs):
"""
Use inspect.signature instead of inspect.getargspec or
inspect.getfullargspec (based on inspect.signature itself) as it provides
more information about function parameters.
.. versionadded: 1.11.2
"""
signature = inspect.signature(func)
parameters = signature.parameters
unexpected = set(kwargs.keys()) - set(parameters.keys())
if len(unexpected) > 0:
return True
params = [
parameter for name, parameter in parameters.items()
if name not in kwargs
]
params_required = [
param for param in params
if param.default is param.empty
]
return not (len(params_required) <= len(args) <= len(params))
def is_invalid_params(func, *args, **kwargs):
"""
Method:
Validate pre-defined criteria, if any is True - function is invalid
0. func should be callable
1. kwargs should not have unexpected keywords
2. remove kwargs.keys from func.parameters
3. number of args should be <= remaining func.parameters
4. number of args should be >= remaining func.parameters less default
"""
# For builtin functions inspect.getargspec(funct) return error. If builtin
# function generates TypeError, it is because of wrong parameters.
if not inspect.isfunction(func):
return True
if sys.version_info >= (3, 3):
return is_invalid_params_py3(func, *args, **kwargs)
else:
# NOTE: use Python2 method for Python 3.2 as well. Starting from Python
# 3.3 it is recommended to use inspect.signature instead.
# In Python 3.0 - 3.2 inspect.getfullargspec is preferred but these
# versions are almost not supported. Users should consider upgrading.
return is_invalid_params_py2(func, *args, **kwargs)