mirror of
https://github.com/dragonpilot/dragonpilot.git
synced 2026-08-08 01:35:40 +08:00
Merge pyextra subtree
This commit is contained in:
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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}'
|
||||
))
|
||||
@@ -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))
|
||||
@@ -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
|
||||
))
|
||||
@@ -0,0 +1 @@
|
||||
""" Tets base JSON-RPC structures."""
|
||||
@@ -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"])
|
||||
@@ -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})
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user