# HG changeset patch # User RhodeCode Admin # Date 2023-03-28 08:21:23 # Node ID 961992a212d02604a08a045a52492371ca907fef # Parent cd6bb3a6140ea4aa80df438ec537e530a68b4d88 api: fixes and changes to always return content type in API - few tweeks here and there diff --git a/rhodecode/api/__init__.py b/rhodecode/api/__init__.py --- a/rhodecode/api/__init__.py +++ b/rhodecode/api/__init__.py @@ -21,10 +21,10 @@ import itertools import logging import sys -import types import fnmatch import decorator +import typing import venusian from collections import OrderedDict @@ -39,7 +39,7 @@ from rhodecode.apps._base import Templat from rhodecode.lib.auth import AuthUser from rhodecode.lib.base import get_ip_addr, attach_context_attributes from rhodecode.lib.exc_tracking import store_exception -from rhodecode.lib.ext_json import json +from rhodecode.lib import ext_json from rhodecode.lib.utils2 import safe_str from rhodecode.lib.plugins.utils import get_plugin_settings from rhodecode.model.db import User, UserApiKeys @@ -64,15 +64,12 @@ def find_methods(jsonrpc_methods, patter class ExtJsonRenderer(object): """ - Custom renderer that mkaes use of our ext_json lib + Custom renderer that makes use of our ext_json lib """ - def __init__(self, serializer=json.dumps, **kw): - """ Any keyword arguments will be passed to the ``serializer`` - function.""" - self.serializer = serializer - self.kw = kw + def __init__(self): + self.serializer = ext_json.formatted_json def __call__(self, info): """ Returns a plain JSON-encoded string with content-type @@ -87,25 +84,17 @@ class ExtJsonRenderer(object): if ct == response.default_content_type: response.content_type = 'application/json' - return self.serializer(value, **self.kw) + return self.serializer(value) return _render def jsonrpc_response(request, result): rpc_id = getattr(request, 'rpc_id', None) - response = request.response - - # store content_type before render is called - ct = response.content_type ret_value = '' if rpc_id: - ret_value = { - 'id': rpc_id, - 'result': result, - 'error': None, - } + ret_value = {'id': rpc_id, 'result': result, 'error': None} # fetch deprecation warnings, and store it inside results deprecation = getattr(request, 'rpc_deprecation', None) @@ -113,30 +102,36 @@ def jsonrpc_response(request, result): ret_value['DEPRECATION_WARNING'] = deprecation raw_body = render(DEFAULT_RENDERER, ret_value, request=request) - response.body = safe_str(raw_body, response.charset) - - if ct == response.default_content_type: - response.content_type = 'application/json' - - return response + content_type = 'application/json' + content_type_header = 'Content-Type' + headers = { + content_type_header: content_type + } + return Response( + body=raw_body, + content_type=content_type, + headerlist=[(k, v) for k, v in headers.items()] + ) -def jsonrpc_error(request, message, retid=None, code=None, headers=None): +def jsonrpc_error(request, message, retid=None, code: typing.Optional[int] = None, headers: typing.Optional[dict] = None): """ Generate a Response object with a JSON-RPC error body + """ + headers = headers or {} + content_type = 'application/json' + content_type_header = 'Content-Type' + if content_type_header not in headers: + headers[content_type_header] = content_type - :param code: - :param retid: - :param message: - """ err_dict = {'id': retid, 'result': None, 'error': message} - body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8') + raw_body = render(DEFAULT_RENDERER, err_dict, request=request) return Response( - body=body, + body=raw_body, status=code, - content_type='application/json', - headerlist=headers + content_type=content_type, + headerlist=[(k, v) for k, v in headers.items()] ) @@ -158,11 +153,11 @@ def exception_view(exc, request): method = request.rpc_method log.debug('json-rpc method `%s` not found in list of ' 'api calls: %s, rpc_id:%s', - method, request.registry.jsonrpc_methods.keys(), rpc_id) + method, list(request.registry.jsonrpc_methods.keys()), rpc_id) similar = 'none' try: - similar_paterns = ['*{}*'.format(x) for x in method.split('_')] + similar_paterns = [f'*{x}*' for x in method.split('_')] similar_found = find_methods( request.registry.jsonrpc_methods, similar_paterns) similar = ', '.join(similar_found.keys()) or similar @@ -222,7 +217,7 @@ def request_view(request): # register our auth-user request.rpc_user = auth_u - request.environ['rc_auth_user_id'] = auth_u.user_id + request.environ['rc_auth_user_id'] = str(auth_u.user_id) # now check if token is valid for API auth_token = request.rpc_api_key @@ -246,10 +241,12 @@ def request_view(request): # now that we have a method, add request._req_params to # self.kargs and dispatch control to WGIController + argspec = inspect.getargspec(func) arglist = argspec[0] - defaults = map(type, argspec[3] or []) - default_empty = types.NotImplementedType + defs = argspec[3] or [] + defaults = [type(a) for a in defs] + default_empty = type(NotImplemented) # kw arguments required by this method func_kwargs = dict(itertools.zip_longest( @@ -285,7 +282,7 @@ def request_view(request): ) # sanitize extra passed arguments - for k in request.rpc_params.keys()[:]: + for k in list(request.rpc_params.keys()): if k not in func_kwargs: del request.rpc_params[k] @@ -313,8 +310,10 @@ def request_view(request): exc_info = sys.exc_info() exc_id, exc_type_name = store_exception( id(exc_info), exc_info, prefix='rhodecode-api') - error_headers = [('RhodeCode-Exception-Id', str(exc_id)), - ('RhodeCode-Exception-Type', str(exc_type_name))] + error_headers = { + 'RhodeCode-Exception-Id': str(exc_id), + 'RhodeCode-Exception-Type': str(exc_type_name) + } err_resp = jsonrpc_error( request, retid=request.rpc_id, message='Internal server error', headers=error_headers) @@ -355,7 +354,7 @@ def setup_request(request): raw_body = request.body log.debug("Loading JSON body now") try: - json_body = json.loads(raw_body) + json_body = ext_json.json.loads(raw_body) except ValueError as e: # catch JSON errors Here raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body)) @@ -382,7 +381,7 @@ def setup_request(request): log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params) except KeyError as e: - raise JSONRPCError('Incorrect JSON data. Missing %s' % e) + raise JSONRPCError(f'Incorrect JSON data. Missing {e}') log.debug('setup complete, now handling method:%s rpcid:%s', request.rpc_method, request.rpc_id, ) @@ -561,8 +560,7 @@ def includeme(config): config.add_view_predicate('jsonrpc_method', MethodPredicate) config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate) - config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer( - serializer=json.dumps, indent=4)) + config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer()) config.add_directive('add_jsonrpc_method', add_jsonrpc_method) config.add_route_predicate( diff --git a/rhodecode/api/tests/test_api.py b/rhodecode/api/tests/test_api.py --- a/rhodecode/api/tests/test_api.py +++ b/rhodecode/api/tests/test_api.py @@ -63,7 +63,7 @@ class TestApi(object): def test_api_missing_non_optional_param_args_null(self): id_, params = build_data(self.apikey, 'get_repo') - params = params.replace('"args": {}', '"args": null') + params = params.replace(b'"args": {}', b'"args": null') response = api_call(self.app, params) expected = 'Missing non optional `repoid` arg in JSON DATA' @@ -71,7 +71,7 @@ class TestApi(object): def test_api_missing_non_optional_param_args_bad(self): id_, params = build_data(self.apikey, 'get_repo') - params = params.replace('"args": {}', '"args": 1') + params = params.replace(b'"args": {}', b'"args": 1') response = api_call(self.app, params) expected = 'Missing non optional `repoid` arg in JSON DATA' @@ -111,13 +111,13 @@ class TestApi(object): def test_api_args_is_null(self): __, params = build_data(self.apikey, 'get_users', ) - params = params.replace('"args": {}', '"args": null') + params = params.replace(b'"args": {}', b'"args": null') response = api_call(self.app, params) assert response.status == '200 OK' def test_api_args_is_bad(self): __, params = build_data(self.apikey, 'get_users', ) - params = params.replace('"args": {}', '"args": 1') + params = params.replace(b'"args": {}', b'"args": 1') response = api_call(self.app, params) assert response.status == '200 OK' diff --git a/rhodecode/api/tests/utils.py b/rhodecode/api/tests/utils.py --- a/rhodecode/api/tests/utils.py +++ b/rhodecode/api/tests/utils.py @@ -86,7 +86,8 @@ def build_data(apikey, method, **kw): def api_call(app, params, status=None): response = app.post( - API_URL, content_type='application/json', params=params, status=status) + API_URL, content_type='application/json', params=params, status=status, + headers=[('Content-Type', 'application/json')]) return response diff --git a/rhodecode/api/views/repo_api.py b/rhodecode/api/views/repo_api.py --- a/rhodecode/api/views/repo_api.py +++ b/rhodecode/api/views/repo_api.py @@ -470,6 +470,8 @@ def get_repo_nodes(request, apiuser, rep ret_type = Optional.extract(ret_type) details = Optional.extract(details) + max_file_bytes = Optional.extract(max_file_bytes) + _extended_types = ['basic', 'full'] if details not in _extended_types: raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types))) @@ -492,6 +494,7 @@ def get_repo_nodes(request, apiuser, rep repo, revision, root_path, flat=False, extended_info=extended_info, content=content, max_file_bytes=max_file_bytes) + _map = { 'all': _d + _f, 'files': _f, diff --git a/rhodecode/api/views/server_api.py b/rhodecode/api/views/server_api.py --- a/rhodecode/api/views/server_api.py +++ b/rhodecode/api/views/server_api.py @@ -347,7 +347,7 @@ def get_method(request, apiuser, pattern argspec = inspect.getargspec(func) arglist = argspec[0] - defaults = map(repr, argspec[3] or []) + defaults = list(map(repr, argspec[3] or [])) default_empty = '' diff --git a/rhodecode/api/views/testing_api.py b/rhodecode/api/views/testing_api.py --- a/rhodecode/api/views/testing_api.py +++ b/rhodecode/api/views/testing_api.py @@ -39,7 +39,7 @@ def test(request, apiuser, args): @jsonrpc_method() def test_ok(request, apiuser): return { - 'who': u'hello {} '.format(apiuser), + 'who': f'hello {apiuser}', 'obj': { 'time': time.time(), 'dt': datetime.datetime.now(), @@ -55,12 +55,15 @@ def test_error(request, apiuser): @jsonrpc_method() def test_exception(request, apiuser): - raise Exception('something unhanddled') + raise Exception('something unhandled') @jsonrpc_method() def test_params(request, apiuser, params): - return u'hello apiuser:{} params:{}'.format(apiuser, params) + return { + 'who': f'hello {apiuser}', + 'params': params + } @jsonrpc_method() @@ -69,16 +72,20 @@ def test_params_opt( opt3=Optional(OAttr('apiuser'))): opt2 = Optional.extract(opt2) opt3 = Optional.extract(opt3, evaluate_locals=locals()) - - return u'hello apiuser:{} params:{}, opt:[{},{},{}]'.format( - apiuser, params, opt1, opt2, opt3) + return { + 'who': f'hello {apiuser}', + 'params': params, + 'opts': [ + opt1, opt2, opt3 + ] + } @jsonrpc_method() @jsonrpc_deprecated_method( use_method='test_ok', deprecated_at_version='4.0.0') def test_deprecated_method(request, apiuser): - return u'value' + return 'value' @jsonrpc_method()