def test_batch_invalid(self): req = json.dumps([{ 'jsonrpc': '2.0', 'method': 'jsonrpc.echoNotFound', 'params': [], 'id': text_type(uuid.uuid4()) }, { 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': [], 'id': text_type(uuid.uuid4()) }, { 'jsonrpc': '2.0', 'method': 'jsonrpc.echoNotFound', 'params': [], 'id': text_type(uuid.uuid4()) }]) resp = self._call(req) self.assertTrue(len(resp) == 3) self.assertNotIn('result', resp[0]) self.assertIn('error', resp[0]) self.assertEqual('Hello Flask JSON-RPC', resp[1]['result']) self.assertNotIn('error', resp[1]) self.assertNotIn('result', resp[2]) self.assertIn('error', resp[2])
def test_batch_call(self): req = json.dumps( [ {"jsonrpc": "2.0", "method": "jsonrpc.echo", "id": text_type(uuid.uuid4())}, {"jsonrpc": "2.0", "method": "jsonrpc.echo", "params": ["Flask"], "id": text_type(uuid.uuid4())}, {"jsonrpc": "2.0", "method": "jsonrpc.echo", "params": None, "id": text_type(uuid.uuid4())}, {"jsonrpc": "2.0", "method": "jsonrpc.echo", "params": [], "id": text_type(uuid.uuid4())}, {"jsonrpc": "2.0", "method": "jsonrpc.echo", "params": {}, "id": text_type(uuid.uuid4())}, {"jsonrpc": "2.0", "method": "jsonrpc.echo", "params": ["フラスコ"], "id": text_type(uuid.uuid4())}, ] ) resp = self._call(req) self.assertTrue(len(resp) == 6) self.assertEqual("Hello Flask JSON-RPC", resp[0]["result"]) self.assertNotIn("error", resp[0]) self.assertEqual("Hello Flask", resp[1]["result"]) self.assertNotIn("error", resp[1]) self.assertEqual("Hello Flask JSON-RPC", resp[2]["result"]) self.assertNotIn("error", resp[2]) self.assertEqual("Hello Flask JSON-RPC", resp[3]["result"]) self.assertNotIn("error", resp[3]) self.assertEqual("Hello Flask JSON-RPC", resp[4]["result"]) self.assertNotIn("error", resp[4]) self.assertEqual("Hello フラスコ", resp[5]["result"]) self.assertNotIn("error", resp[5])
def extract_raw_data_request(request): def _extract_raw_data_request(request): if request.method == 'GET': return request.query_string elif request.method == 'POST': if request.data: return request.data elif request.form.to_dict(): return list(request.form.to_dict().keys())[0] return b('') raw_data = _extract_raw_data_request(request) tried_encodings = [] # Try charset from content-type encoding = request.charset if request.charset else 'utf-8' if encoding: try: return text_type(raw_data, encoding) except UnicodeError: tried_encodings.append(encoding) # Fall back: try: return text_type(raw_data, encoding, errors='replace') except TypeError: return raw_data
def extract_raw_data_request(request): def _extract_raw_data_request(request): if request.method == 'GET': return request.query_string elif request.method == 'POST': if request.data: return request.data elif request.form.to_dict(): #return list(request.form.to_dict().keys())[0] return list(request.form.to_dict().keys())[0] if isinstance(list(request.form.to_dict().keys())[0], bytes) else list(request.form.to_dict().keys())[0].encode() return b('') raw_data = _extract_raw_data_request(request) tried_encodings = [] # Try charset from content-type encoding = request.charset if request.charset else 'utf-8' if encoding: try: return text_type(raw_data, encoding) except UnicodeError: tried_encodings.append(encoding) # Fall back: try: return text_type(raw_data, encoding, errors='replace') except TypeError: return raw_data
def procedure_desc(self, key): M = self.urls[key] return { 'name': M.json_method, 'summary': M.__doc__, 'idempotent': M.json_safe, 'params': [{'type': text_type(Any.kind(t)), 'name': k} for k, t in iteritems(M.json_arg_types)], 'return': {'type': text_type(Any.kind(M.json_return_type))}}
def _parse_sig(sig, arg_names, validate=False): """Parses signatures into a ``OrderedDict`` of paramName => type. Numerically-indexed arguments that do not correspond to an argument name in python (ie: it takes a variable number of arguments) will be keyed as the stringified version of it's index. sig the signature to be parsed arg_names a list of argument names extracted from python source Returns a tuple of (method name, types dict, return type) """ d = SIG_RE.match(sig) if not d: raise ValueError('Invalid method signature {0}'.format(sig)) d = d.groupdict() ret = [(n, Any) for n in arg_names] if text_type('args_sig') in d and type( d['args_sig']) in string_types and d['args_sig'].strip(): for i, arg in enumerate(d['args_sig'].strip().split(',')): _type_checking_available(sig, validate) if text_type('=') in arg: if not type(ret) is OrderedDict: ret = OrderedDict(ret) dk = KWARG_RE.match(arg) if not dk: raise ValueError( 'Could not parse arg type {0} in {1}'.format(arg, sig)) dk = dk.groupdict() if not sum([(k in dk and type(dk[k]) in string_types and bool(dk[k].strip())) for k in ('arg_name', 'arg_type')]): raise ValueError('Invalid kwarg value {0} in {1}'.format( arg, sig)) ret[dk['arg_name']] = _eval_arg_type(dk['arg_type'], None, arg, sig) else: if type(ret) is OrderedDict: raise ValueError( 'Positional arguments must occur ' 'before keyword arguments in {0}'.format(sig)) if len(ret) < i + 1: ret.append( (text_type(i), _eval_arg_type(arg, None, arg, sig))) else: ret[i] = (ret[i][0], _eval_arg_type(arg, None, arg, sig)) if not type(ret) is OrderedDict: ret = OrderedDict(ret) return (d['method_name'], ret, (_eval_arg_type( d['return_sig'], Any, 'return', sig) if d['return_sig'] else Any))
def _make_payload(self, method, params=None, version='1.0', is_notify=False): return json.dumps({ 'jsonrpc': version, 'method': method, 'params': params if params else [], 'id': None if is_notify else text_type(uuid.uuid1()) })
def json_rpc_format(self): """Return the Exception data in a format for JSON-RPC """ error = { 'name': text_type(self.__class__.__name__), 'code': self.code, 'message': '{0}: {1}'.format(text_type(self.__class__.__name__), text_type(self.message)), 'data': self.data } if current_app.config['DEBUG']: import sys, traceback error['stack'] = traceback.format_exc() error['executable'] = sys.executable return error
def test_index(self): with self.app: r = self.app.get('/api/browse/') data = text_type(r.data) self.assertEqual(200, r.status_code) self.assertIn('Flask JSON-RPC', data) self.assertIn('Web browsable API', data) self.assertIn('https://github.com/cenobites/flask-jsonrpc', data)
def json_rpc_format(self): """Return the Exception data in a format for JSON-RPC """ error = { 'name': text_type(self.__class__.__name__), 'code': self.code, 'message': '{0}'.format(text_type(self.message)), 'data': self.data } if current_app.config['DEBUG']: import sys, traceback error['stack'] = str(traceback.format_exc()).replace("\"","'") error['executable'] = sys.executable return error
def __init__(self, service_url, service_name=None, version=JSONRPC_VERSION_DEFAULT, headers=None): self.version = text_type(version) self.service_url = service_url self.service_name = service_name self.headers = headers or self.DEFAULT_HEADERS
def _make_payload(self, method, params=None, version="1.0", is_notify=False): return json.dumps( { "jsonrpc": version, "method": method, "params": params if params else [], "id": None if is_notify else text_type(uuid.uuid4()), } )
def _parse_sig(sig, arg_names, validate=False): """Parses signatures into a ``OrderedDict`` of paramName => type. Numerically-indexed arguments that do not correspond to an argument name in python (ie: it takes a variable number of arguments) will be keyed as the stringified version of it's index. sig the signature to be parsed arg_names a list of argument names extracted from python source Returns a tuple of (method name, types dict, return type) """ d = SIG_RE.match(sig) if not d: raise ValueError('Invalid method signature {0}'.format(sig)) d = d.groupdict() ret = [(n, Any) for n in arg_names] if text_type('args_sig') in d and type(d['args_sig']) in string_types and d['args_sig'].strip(): for i, arg in enumerate(d['args_sig'].strip().split(',')): _type_checking_available(sig, validate) if text_type('=') in arg: if not type(ret) is OrderedDict: ret = OrderedDict(ret) dk = KWARG_RE.match(arg) if not dk: raise ValueError('Could not parse arg type {0} in {1}'.format(arg, sig)) dk = dk.groupdict() if not sum([(k in dk and type(dk[k]) in string_types and bool(dk[k].strip())) for k in ('arg_name', 'arg_type')]): raise ValueError('Invalid kwarg value {0} in {1}'.format(arg, sig)) ret[dk['arg_name']] = _eval_arg_type(dk['arg_type'], None, arg, sig) else: if type(ret) is OrderedDict: raise ValueError('Positional arguments must occur ' 'before keyword arguments in {0}'.format(sig)) if len(ret) < i + 1: ret.append((text_type(i), _eval_arg_type(arg, None, arg, sig))) else: ret[i] = (ret[i][0], _eval_arg_type(arg, None, arg, sig)) if not type(ret) is OrderedDict: ret = OrderedDict(ret) return (d['method_name'], ret, (_eval_arg_type(d['return_sig'], Any, 'return', sig) if d['return_sig'] else Any))
def service_desc(self): return { 'sdversion': '1.0', 'name': self.name, 'id': 'urn:uuid:{0}'.format(text_type(self.uuid)), 'summary': self.__doc__, 'version': self.version, 'procs': [self.procedure_desc(k) for k in iterkeys(self.urls) if self.urls[k] != self.describe]}
def send_payload(self, params): dump = json.dumps({ 'jsonrpc': self.version, 'method': self.service_name, 'params': params, 'id': text_type(uuid.uuid4()) }) dump_payload = FakePayload(dump) response = current_app.post(self.service_url, **{'wsgi.input' : dump_payload, 'CONTENT_LENGTH' : len(dump)}) return response.content
def send_payload(self, params): """Performs the actual sending action and returns the result """ data = json.dumps({ 'jsonrpc': self.version, 'method': self.service_name, 'params': params, 'id': text_type(uuid.uuid1()) }) data_binary = data.encode('utf-8') return urlopen(self.service_url, data_binary).read()
def send_payload(self, params): dump = json.dumps({ 'jsonrpc': self.version, 'method': self.service_name, 'params': params, 'id': text_type(uuid.uuid1()) }) dump_payload = FakePayload(dump) response = current_app.post(self.service_url, **{'wsgi.input' : dump_payload, 'CONTENT_LENGTH' : len(dump)}) return response.content
def validate_get(self, request, method): encode_get_params = lambda r: dict([(k, v[0] if len(v) == 1 else v) for k, v in r]) if request.method == 'GET': method = text_type(method) if method in self.urls and getattr(self.urls[method], 'json_safe', False): D = { 'params': request.args.to_dict(), 'method': method, 'id': 'jsonrpc', 'version': '1.1' } return True, D return False, {}
def extract_id_request(self, raw_data): try: D = json.loads(raw_data) return D.get('id') except Exception as e: if not raw_data is None and raw_data.find('id') != -1: find_id = re.findall(r'["|\']id["|\']:([0-9]+)|["|\']id["|\']:["|\'](.+?)["|\']', raw_data.replace(' ', ''), re.U) if find_id: g1, g2 = find_id[0] raw_id = g1 if g1 else g2 if text_type(raw_id).isnumeric(): return int(raw_id) return raw_id return None
def encode_arg11(p): if type(p) is list: return p elif not type(p) is dict: return [] else: pos = [] d = encode_kw(p) for k, v in iteritems(d): try: pos.append(int(k)) except ValueError: pass pos = list(set(pos)) pos.sort() return [d[text_type(i)] for i in pos]
def test_batch_invalid(self): req = json.dumps( [ {"jsonrpc": "2.0", "method": "jsonrpc.echoNotFound", "params": [], "id": text_type(uuid.uuid4())}, {"jsonrpc": "2.0", "method": "jsonrpc.echo", "params": [], "id": text_type(uuid.uuid4())}, {"jsonrpc": "2.0", "method": "jsonrpc.echoNotFound", "params": [], "id": text_type(uuid.uuid4())}, ] ) resp = self._call(req) self.assertTrue(len(resp) == 3) self.assertNotIn("result", resp[0]) self.assertIn("error", resp[0]) self.assertEqual("Hello Flask JSON-RPC", resp[1]["result"]) self.assertNotIn("error", resp[1]) self.assertNotIn("result", resp[2]) self.assertIn("error", resp[2])
def _eval_arg_type(arg_type, T=Any, arg=None, sig=None): """Returns a type from a snippit of python source. Should normally be something just like 'str' or 'Object'. arg_type the source to be evaluated T the default type arg context of where this type was extracted sig context from where the arg was extracted Returns a type or a Type """ try: T = eval(arg_type) except Exception as e: raise ValueError('The type of {0} could not be evaluated in {1} for {2}: {3}' \ .format(arg_type, arg, sig, text_type(e))) else: if type(T) not in (type, Type): raise TypeError('{0} is not a valid type in {1} for {2}' \ .format(repr(T), arg, sig)) return T
def test_batch_call(self): req = json.dumps([{ 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'id': text_type(uuid.uuid4()) }, { 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': ['Flask'], 'id': text_type(uuid.uuid4()) }, { 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': None, 'id': text_type(uuid.uuid4()) }, { 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': [], 'id': text_type(uuid.uuid4()) }, { 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': {}, 'id': text_type(uuid.uuid4()) }, { 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': ['フラスコ'], 'id': text_type(uuid.uuid4()) }]) resp = self._call(req) self.assertTrue(len(resp) == 6) self.assertEqual('Hello Flask JSON-RPC', resp[0]['result']) self.assertNotIn('error', resp[0]) self.assertEqual('Hello Flask', resp[1]['result']) self.assertNotIn('error', resp[1]) self.assertEqual('Hello Flask JSON-RPC', resp[2]['result']) self.assertNotIn('error', resp[2]) self.assertEqual('Hello Flask JSON-RPC', resp[3]['result']) self.assertNotIn('error', resp[3]) self.assertEqual('Hello Flask JSON-RPC', resp[4]['result']) self.assertNotIn('error', resp[4]) self.assertEqual('Hello フラスコ', resp[5]['result']) self.assertNotIn('error', resp[5])
from flask_jsonrpc.exceptions import (Error, ParseError, InvalidRequestError, MethodNotFoundError, InvalidParamsError, ServerError, RequestPostError, InvalidCredentialsError, OtherError) JSONRPC_VERSION_DEFAULT = '2.0' empty_dec = lambda f: f try: # TODO: Add CSRF check csrf_exempt = empty_dec except (NameError, ImportError): csrf_exempt = empty_dec NoneType = type(None) encode_kw = lambda p: dict([(text_type(k), v) for k, v in iteritems(p)]) def encode_kw11(p): if not type(p) is dict: return {} ret = p.copy() removes = [] for k, v in iteritems(ret): try: int(k) except ValueError: pass else: removes.append(k) for k in removes: ret.pop(k)
def decode(self, n): return reduce( lambda L, R: R if (text_type(R) == n) else L, _types_gen(self))
def test_response_object(self): with self.app: r = self.app.get('/api/browse/partials/response_object.html') data = text_type(r.data) self.assertEqual(200, r.status_code) self.assertIn('HTTP', data)
def __init__(self, service_url, service_name=None, version="2.0"): self.version = text_type(version) self.service_url = service_url self.service_name = service_name
def test_partials_dashboard(self): with self.app: r = self.app.get('/api/browse/partials/dashboard.html') data = text_type(r.data) self.assertEqual(200, r.status_code) self.assertIn('Welcome to web browsable API', data)
def response_obj(self, request, D, version_hint=JSONRPC_VERSION_DEFAULT): version = version_hint response = self.empty_response(version=version) apply_version = { '2.0': self.apply_version_2_0, '1.1': self.apply_version_1_1, '1.0': self.apply_version_1_0 } try: try: # determine if an object is iterable? iter(D) except TypeError as e: raise InvalidRequestError( getattr(e, 'message', e.args[0] if len(e.args) > 0 else None)) # version: validate if 'jsonrpc' in D: if text_type(D['jsonrpc']) not in apply_version: raise InvalidRequestError( 'JSON-RPC version {0} not supported.'.format( D['jsonrpc'])) version = request.jsonrpc_version = response[ 'jsonrpc'] = text_type(D['jsonrpc']) elif 'version' in D: if text_type(D['version']) not in apply_version: raise InvalidRequestError( 'JSON-RPC version {0} not supported.'.format( D['version'])) version = request.jsonrpc_version = response[ 'version'] = text_type(D['version']) else: version = request.jsonrpc_version = JSONRPC_VERSION_DEFAULT # params: An Array or Object, that holds the actual parameter values # for the invocation of the procedure. Can be omitted if empty. if 'params' not in D or not D['params']: D['params'] = [] if 'method' not in D or 'params' not in D: raise InvalidParamsError( 'Request requires str:"method" and list:"params"') if D['method'] not in self.urls: raise MethodNotFoundError('Method not found. Available methods: {0}' \ .format('\n'.join(list(self.urls.keys())))) method = self.urls[text_type(D['method'])] if getattr(method, 'json_validate', False): validate_params(method, D) if 'id' in D and D['id'] is not None: # regular request response['id'] = D['id'] if version in ('1.1', '2.0'): response.pop('error', None) else: # notification return None, 204 R = apply_version[version](method, D['params']) if 'id' not in D or ('id' in D and D['id'] is None): # notification return None, 204 if isinstance(R, Response): if R.status_code == 200: return R, R.status_code if R.status_code == 401: raise InvalidCredentialsError(R.status) raise OtherError(R.status, R.status_code) try: # New in Flask version 0.10. encoder = current_app.json_encoder() except AttributeError: encoder = json.JSONEncoder() # type of `R` should be one of these or... if not sum([isinstance(R, e) for e in \ string_types + integer_types + \ (float, complex, dict, list, tuple, set, frozenset, NoneType, bool)]): try: rs = encoder.default( R) # ...or something this thing supports except TypeError as exc: raise TypeError( 'Return type not supported, for {0!r}'.format(R)) response['result'] = R status = 200 except Error as e: response['error'] = e.json_rpc_format if version in ('1.1', '2.0'): response.pop('result', None) status = e.status except HTTPException as e: other_error = OtherError(e) response['error'] = other_error.json_rpc_format response['error']['code'] = e.code if version in ('1.1', '2.0'): response.pop('result', None) status = e.code except Exception as e: other_error = OtherError(e) response['error'] = other_error.json_rpc_format status = other_error.status if version in ('1.1', '2.0'): response.pop('result', None) # Exactly one of result or error MUST be specified. It's not # allowed to specify both or none. if version in ('1.1', '2.0') and 'result' in response: response.pop('error', None) return response, status
def register(self, name, method): self.urls[text_type(name)] = method
def __init__(self, service_url, service_name=None, version='2.0'): self.version = text_type(version) self.service_url = service_url self.service_name = service_name
def __init__(self): self.urls = {} self.uuid = text_type(uuid4()) self.version = JSONRPC_VERSION_DEFAULT self.name = 'Flask-JSONRPC' self.register('system.describe', self.describe)
def response_obj(self, request, D, version_hint=JSONRPC_VERSION_DEFAULT): version = version_hint response = self.empty_response(version=version) apply_version = { '2.0': self.apply_version_2_0, '1.1': self.apply_version_1_1, '1.0': self.apply_version_1_0 } try: try: # determine if an object is iterable? iter(D) except TypeError as e: raise InvalidRequestError(getattr(e, 'message', e.args[0] if len(e.args) > 0 else None)) # version: validate if 'jsonrpc' in D: if text_type(D['jsonrpc']) not in apply_version: raise InvalidRequestError('JSON-RPC version {0} not supported.'.format(D['jsonrpc'])) version = request.jsonrpc_version = response['jsonrpc'] = text_type(D['jsonrpc']) elif 'version' in D: if text_type(D['version']) not in apply_version: raise InvalidRequestError('JSON-RPC version {0} not supported.'.format(D['version'])) version = request.jsonrpc_version = response['version'] = text_type(D['version']) else: version = request.jsonrpc_version = JSONRPC_VERSION_DEFAULT # params: An Array or Object, that holds the actual parameter values # for the invocation of the procedure. Can be omitted if empty. if 'params' not in D or not D['params']: D['params'] = [] if 'method' not in D or 'params' not in D: raise InvalidParamsError('Request requires str:"method" and list:"params"') if D['method'] not in self.urls: raise MethodNotFoundError('Method not found. Available methods: {0}' \ .format('\n'.join(list(self.urls.keys())))) method = self.urls[text_type(D['method'])] if getattr(method, 'json_validate', False): validate_params(method, D) if 'id' in D and D['id'] is not None: # regular request response['id'] = D['id'] if version in ('1.1', '2.0'): response.pop('error', None) else: # notification return None, 204 R = apply_version[version](method, D['params']) if 'id' not in D or ('id' in D and D['id'] is None): # notification return None, 204 if isinstance(R, Response): if R.status_code == 200: return R, R.status_code if R.status_code == 401: raise InvalidCredentialsError(R.status) raise OtherError(R.status, R.status_code) try: # New in Flask version 0.10. encoder = current_app.json_encoder() except AttributeError: encoder = json.JSONEncoder() # type of `R` should be one of these or... if not sum([isinstance(R, e) for e in \ string_types + integer_types + \ (float, complex, dict, list, tuple, set, frozenset, NoneType, bool)]): try: rs = encoder.default(R) # ...or something this thing supports except TypeError as exc: raise TypeError('Return type not supported, for {0!r}'.format(R)) response['result'] = R status = 200 except Error as e: response['error'] = e.json_rpc_format if version in ('1.1', '2.0'): response.pop('result', None) status = e.status except HTTPException as e: other_error = OtherError(e) response['error'] = other_error.json_rpc_format response['error']['code'] = e.code if version in ('1.1', '2.0'): response.pop('result', None) status = e.code except Exception as e: other_error = OtherError(e) response['error'] = other_error.json_rpc_format status = other_error.status if version in ('1.1', '2.0'): response.pop('result', None) # Exactly one of result or error MUST be specified. It's not # allowed to specify both or none. if version in ('1.1', '2.0') and 'result' in response: response.pop('error', None) return response, status
def send_payload(self, params): dump = json.dumps( {"jsonrpc": self.version, "method": self.service_name, "params": params, "id": text_type(uuid.uuid1())} ) dump_payload = FakePayload(dump) response = current_app.post(self.service_url, **{"wsgi.input": dump_payload, "CONTENT_LENGTH": len(dump)}) return response.content
def response_dict(self, request, D, is_batch=False, version_hint='1.0'): version = version_hint response = self.empty_response(version=version) apply_version = { '2.0': lambda f, p: f(**encode_kw(p)) if type(p) is dict else f(*p), '1.1': lambda f, p: f(*encode_arg11(p), **encode_kw(encode_kw11(p))), '1.0': lambda f, p: f(*p) } try: # params: An Array or Object, that holds the actual parameter values # for the invocation of the procedure. Can be omitted if empty. if 'params' not in D or not D['params']: D['params'] = [] if 'method' not in D or 'params' not in D: raise InvalidParamsError('Request requires str:"method" and list:"params"') if D['method'] not in self.urls: raise MethodNotFoundError('Method not found. Available methods: {0}' \ .format('\n'.join(list(self.urls.keys())))) if 'jsonrpc' in D: if text_type(D['jsonrpc']) not in apply_version: raise InvalidRequestError('JSON-RPC version {0} not supported.'.format(D['jsonrpc'])) version = request.jsonrpc_version = response['jsonrpc'] = text_type(D['jsonrpc']) elif 'version' in D: if text_type(D['version']) not in apply_version: raise InvalidRequestError('JSON-RPC version {0} not supported.'.format(D['version'])) version = request.jsonrpc_version = response['version'] = text_type(D['version']) else: request.jsonrpc_version = '1.0' method = self.urls[text_type(D['method'])] if getattr(method, 'json_validate', False): validate_params(method, D) if 'id' in D and D['id'] is not None: # regular request response['id'] = D['id'] if version in ('1.1', '2.0') and 'error' in response: response.pop('error') elif is_batch: # notification, not ok in a batch format, but happened anyway raise InvalidRequestError else: # notification return None, 204 R = apply_version[version](method, D['params']) if 'id' not in D or ('id' in D and D['id'] is None): # notification return None, 204 if isinstance(R, Response): if R.status_code == 401: raise InvalidCredentialsError(R.status) raise OtherError(R.status, R.status_code) encoder = current_app.json_encoder() # type of `R` should be one of these or... if not sum([isinstance(R, e) for e in \ string_types + integer_types + (dict, list, set, NoneType, bool)]): try: rs = encoder.default(R) # ...or something this thing supports except TypeError as exc: raise TypeError('Return type not supported, for {0!r}'.format(R)) response['result'] = R status = 200 except Error as e: response['error'] = e.json_rpc_format if version in ('1.1', '2.0') and 'result' in response: response.pop('result') status = e.status except HTTPException as e: other_error = OtherError(e) response['error'] = other_error.json_rpc_format response['error']['code'] = e.code if version in ('1.1', '2.0') and 'result' in response: response.pop('result') status = e.code except Exception as e: other_error = OtherError(e) response['error'] = other_error.json_rpc_format status = other_error.status if version in ('1.1', '2.0') and 'result' in response: response.pop('result') # Exactly one of result or error MUST be specified. It's not # allowed to specify both or none. if version in ('1.1', '2.0') and 'error' in response and not response['error']: response.pop('error') return response, status
def decode(self, n): return reduce(lambda L, R: R if (text_type(R) == n) else L, _types_gen(self))
def response_dict(self, request, D, is_batch=False, version_hint='1.0'): version = version_hint response = self.empty_response(version=version) apply_version = { '2.0': lambda f, p: f(**encode_kw(p)) if type(p) is dict else f(*p), '1.1': lambda f, p: f(*encode_arg11(p), **encode_kw(encode_kw11(p))), '1.0': lambda f, p: f(*p) } try: # params: An Array or Object, that holds the actual parameter values # for the invocation of the procedure. Can be omitted if empty. if 'params' not in D or not D['params']: D['params'] = [] if 'method' not in D or 'params' not in D: raise InvalidParamsError( 'Request requires str:"method" and list:"params"') if D['method'] not in self.urls: raise MethodNotFoundError('Method not found. Available methods: {0}' \ .format('\n'.join(list(self.urls.keys())))) if 'jsonrpc' in D: if text_type(D['jsonrpc']) not in apply_version: raise InvalidRequestError( 'JSON-RPC version {0} not supported.'.format( D['jsonrpc'])) version = request.jsonrpc_version = response[ 'jsonrpc'] = text_type(D['jsonrpc']) elif 'version' in D: if text_type(D['version']) not in apply_version: raise InvalidRequestError( 'JSON-RPC version {0} not supported.'.format( D['version'])) version = request.jsonrpc_version = response[ 'version'] = text_type(D['version']) else: request.jsonrpc_version = '1.0' method = self.urls[text_type(D['method'])] if getattr(method, 'json_validate', False): validate_params(method, D) if 'id' in D and D['id'] is not None: # regular request response['id'] = D['id'] if version in ('1.1', '2.0') and 'error' in response: response.pop('error') elif is_batch: # notification, not ok in a batch format, but happened anyway raise InvalidRequestError else: # notification return None, 204 R = apply_version[version](method, D['params']) if asyncio.iscoroutine(R): R = (yield from R) if 'id' not in D or ('id' in D and D['id'] is None): # notification return None, 204 encoder = current_app.json_encoder() # type of `R` should be one of these or... if not sum([isinstance(R, e) for e in \ string_types + integer_types + (dict, list, set, NoneType, bool)]): try: rs = encoder.default( R) # ...or something this thing supports except TypeError as exc: raise TypeError( "Return type not supported, for {0!r}".format(R)) response['result'] = R status = 200 except Error as e: # exception missed by others #got_request_exception.connect(log_exception, current_app._get_current_object()) response['error'] = e.json_rpc_format if version in ('1.1', '2.0') and 'result' in response: response.pop('result') status = e.status except HTTPException as e: # exception missed by others #got_request_exception.connect(log_exception, current_app._get_current_object()) other_error = OtherError(e) response['error'] = other_error.json_rpc_format response['error']['code'] = e.code if version in ('1.1', '2.0') and 'result' in response: response.pop('result') status = e.code except Exception as e: # exception missed by others #got_request_exception.connect(log_exception, current_app._get_current_object()) other_error = OtherError(e) response['error'] = other_error.json_rpc_format status = other_error.status if version in ('1.1', '2.0') and 'result' in response: response.pop('result') # Exactly one of result or error MUST be specified. It's not # allowed to specify both or none. if version in ('1.1', '2.0' ) and 'error' in response and not response['error']: response.pop('error') return response, status
from flask_jsonrpc.exceptions import (Error, ParseError, InvalidRequestError, MethodNotFoundError, InvalidParamsError, ServerError, RequestPostError, InvalidCredentialsError, OtherError) JSONRPC_VERSION_DEFAULT = '2.0' empty_dec = lambda f: f try: # TODO: Add CSRF check csrf_exempt = empty_dec except (NameError, ImportError): csrf_exempt = empty_dec NoneType = type(None) encode_kw = lambda p: dict([(text_type(k), v) for k, v in iteritems(p)]) def encode_kw11(p): if not type(p) is dict: return {} ret = p.copy() removes = [] for k, v in iteritems(ret): try: int(k) except ValueError: pass else: removes.append(k) for k in removes:
def __init__(self): self.urls = {} self.uuid = text_type(uuid1()) self.version = '1.0' self.name = 'Flask-JSONRPC' self.register('system.describe', self.describe)
def send_payload(self, params): """Performs the actual sending action and returns the result """ data = json.dumps( {"jsonrpc": self.version, "method": self.service_name, "params": params, "id": text_type(uuid.uuid1())} ) data_binary = data.encode("utf-8") return urlopen(self.service_url, data_binary).read()