##// END OF EJS Templates
api: fixes and changes to always return content type in API...
super-admin -
r5001:961992a2 default
parent child Browse files
Show More
@@ -21,10 +21,10 b''
21 21 import itertools
22 22 import logging
23 23 import sys
24 import types
25 24 import fnmatch
26 25
27 26 import decorator
27 import typing
28 28 import venusian
29 29 from collections import OrderedDict
30 30
@@ -39,7 +39,7 b' from rhodecode.apps._base import Templat'
39 39 from rhodecode.lib.auth import AuthUser
40 40 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
41 41 from rhodecode.lib.exc_tracking import store_exception
42 from rhodecode.lib.ext_json import json
42 from rhodecode.lib import ext_json
43 43 from rhodecode.lib.utils2 import safe_str
44 44 from rhodecode.lib.plugins.utils import get_plugin_settings
45 45 from rhodecode.model.db import User, UserApiKeys
@@ -64,15 +64,12 b' def find_methods(jsonrpc_methods, patter'
64 64
65 65 class ExtJsonRenderer(object):
66 66 """
67 Custom renderer that mkaes use of our ext_json lib
67 Custom renderer that makes use of our ext_json lib
68 68
69 69 """
70 70
71 def __init__(self, serializer=json.dumps, **kw):
72 """ Any keyword arguments will be passed to the ``serializer``
73 function."""
74 self.serializer = serializer
75 self.kw = kw
71 def __init__(self):
72 self.serializer = ext_json.formatted_json
76 73
77 74 def __call__(self, info):
78 75 """ Returns a plain JSON-encoded string with content-type
@@ -87,25 +84,17 b' class ExtJsonRenderer(object):'
87 84 if ct == response.default_content_type:
88 85 response.content_type = 'application/json'
89 86
90 return self.serializer(value, **self.kw)
87 return self.serializer(value)
91 88
92 89 return _render
93 90
94 91
95 92 def jsonrpc_response(request, result):
96 93 rpc_id = getattr(request, 'rpc_id', None)
97 response = request.response
98
99 # store content_type before render is called
100 ct = response.content_type
101 94
102 95 ret_value = ''
103 96 if rpc_id:
104 ret_value = {
105 'id': rpc_id,
106 'result': result,
107 'error': None,
108 }
97 ret_value = {'id': rpc_id, 'result': result, 'error': None}
109 98
110 99 # fetch deprecation warnings, and store it inside results
111 100 deprecation = getattr(request, 'rpc_deprecation', None)
@@ -113,30 +102,36 b' def jsonrpc_response(request, result):'
113 102 ret_value['DEPRECATION_WARNING'] = deprecation
114 103
115 104 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
116 response.body = safe_str(raw_body, response.charset)
117
118 if ct == response.default_content_type:
119 response.content_type = 'application/json'
120
121 return response
105 content_type = 'application/json'
106 content_type_header = 'Content-Type'
107 headers = {
108 content_type_header: content_type
109 }
110 return Response(
111 body=raw_body,
112 content_type=content_type,
113 headerlist=[(k, v) for k, v in headers.items()]
114 )
122 115
123 116
124 def jsonrpc_error(request, message, retid=None, code=None, headers=None):
117 def jsonrpc_error(request, message, retid=None, code: typing.Optional[int] = None, headers: typing.Optional[dict] = None):
125 118 """
126 119 Generate a Response object with a JSON-RPC error body
120 """
121 headers = headers or {}
122 content_type = 'application/json'
123 content_type_header = 'Content-Type'
124 if content_type_header not in headers:
125 headers[content_type_header] = content_type
127 126
128 :param code:
129 :param retid:
130 :param message:
131 """
132 127 err_dict = {'id': retid, 'result': None, 'error': message}
133 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
128 raw_body = render(DEFAULT_RENDERER, err_dict, request=request)
134 129
135 130 return Response(
136 body=body,
131 body=raw_body,
137 132 status=code,
138 content_type='application/json',
139 headerlist=headers
133 content_type=content_type,
134 headerlist=[(k, v) for k, v in headers.items()]
140 135 )
141 136
142 137
@@ -158,11 +153,11 b' def exception_view(exc, request):'
158 153 method = request.rpc_method
159 154 log.debug('json-rpc method `%s` not found in list of '
160 155 'api calls: %s, rpc_id:%s',
161 method, request.registry.jsonrpc_methods.keys(), rpc_id)
156 method, list(request.registry.jsonrpc_methods.keys()), rpc_id)
162 157
163 158 similar = 'none'
164 159 try:
165 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
160 similar_paterns = [f'*{x}*' for x in method.split('_')]
166 161 similar_found = find_methods(
167 162 request.registry.jsonrpc_methods, similar_paterns)
168 163 similar = ', '.join(similar_found.keys()) or similar
@@ -222,7 +217,7 b' def request_view(request):'
222 217
223 218 # register our auth-user
224 219 request.rpc_user = auth_u
225 request.environ['rc_auth_user_id'] = auth_u.user_id
220 request.environ['rc_auth_user_id'] = str(auth_u.user_id)
226 221
227 222 # now check if token is valid for API
228 223 auth_token = request.rpc_api_key
@@ -246,10 +241,12 b' def request_view(request):'
246 241
247 242 # now that we have a method, add request._req_params to
248 243 # self.kargs and dispatch control to WGIController
244
249 245 argspec = inspect.getargspec(func)
250 246 arglist = argspec[0]
251 defaults = map(type, argspec[3] or [])
252 default_empty = types.NotImplementedType
247 defs = argspec[3] or []
248 defaults = [type(a) for a in defs]
249 default_empty = type(NotImplemented)
253 250
254 251 # kw arguments required by this method
255 252 func_kwargs = dict(itertools.zip_longest(
@@ -285,7 +282,7 b' def request_view(request):'
285 282 )
286 283
287 284 # sanitize extra passed arguments
288 for k in request.rpc_params.keys()[:]:
285 for k in list(request.rpc_params.keys()):
289 286 if k not in func_kwargs:
290 287 del request.rpc_params[k]
291 288
@@ -313,8 +310,10 b' def request_view(request):'
313 310 exc_info = sys.exc_info()
314 311 exc_id, exc_type_name = store_exception(
315 312 id(exc_info), exc_info, prefix='rhodecode-api')
316 error_headers = [('RhodeCode-Exception-Id', str(exc_id)),
317 ('RhodeCode-Exception-Type', str(exc_type_name))]
313 error_headers = {
314 'RhodeCode-Exception-Id': str(exc_id),
315 'RhodeCode-Exception-Type': str(exc_type_name)
316 }
318 317 err_resp = jsonrpc_error(
319 318 request, retid=request.rpc_id, message='Internal server error',
320 319 headers=error_headers)
@@ -355,7 +354,7 b' def setup_request(request):'
355 354 raw_body = request.body
356 355 log.debug("Loading JSON body now")
357 356 try:
358 json_body = json.loads(raw_body)
357 json_body = ext_json.json.loads(raw_body)
359 358 except ValueError as e:
360 359 # catch JSON errors Here
361 360 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
@@ -382,7 +381,7 b' def setup_request(request):'
382 381
383 382 log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params)
384 383 except KeyError as e:
385 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
384 raise JSONRPCError(f'Incorrect JSON data. Missing {e}')
386 385
387 386 log.debug('setup complete, now handling method:%s rpcid:%s',
388 387 request.rpc_method, request.rpc_id, )
@@ -561,8 +560,7 b' def includeme(config):'
561 560 config.add_view_predicate('jsonrpc_method', MethodPredicate)
562 561 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
563 562
564 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
565 serializer=json.dumps, indent=4))
563 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer())
566 564 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
567 565
568 566 config.add_route_predicate(
@@ -63,7 +63,7 b' class TestApi(object):'
63 63
64 64 def test_api_missing_non_optional_param_args_null(self):
65 65 id_, params = build_data(self.apikey, 'get_repo')
66 params = params.replace('"args": {}', '"args": null')
66 params = params.replace(b'"args": {}', b'"args": null')
67 67 response = api_call(self.app, params)
68 68
69 69 expected = 'Missing non optional `repoid` arg in JSON DATA'
@@ -71,7 +71,7 b' class TestApi(object):'
71 71
72 72 def test_api_missing_non_optional_param_args_bad(self):
73 73 id_, params = build_data(self.apikey, 'get_repo')
74 params = params.replace('"args": {}', '"args": 1')
74 params = params.replace(b'"args": {}', b'"args": 1')
75 75 response = api_call(self.app, params)
76 76
77 77 expected = 'Missing non optional `repoid` arg in JSON DATA'
@@ -111,13 +111,13 b' class TestApi(object):'
111 111
112 112 def test_api_args_is_null(self):
113 113 __, params = build_data(self.apikey, 'get_users', )
114 params = params.replace('"args": {}', '"args": null')
114 params = params.replace(b'"args": {}', b'"args": null')
115 115 response = api_call(self.app, params)
116 116 assert response.status == '200 OK'
117 117
118 118 def test_api_args_is_bad(self):
119 119 __, params = build_data(self.apikey, 'get_users', )
120 params = params.replace('"args": {}', '"args": 1')
120 params = params.replace(b'"args": {}', b'"args": 1')
121 121 response = api_call(self.app, params)
122 122 assert response.status == '200 OK'
123 123
@@ -86,7 +86,8 b' def build_data(apikey, method, **kw):'
86 86
87 87 def api_call(app, params, status=None):
88 88 response = app.post(
89 API_URL, content_type='application/json', params=params, status=status)
89 API_URL, content_type='application/json', params=params, status=status,
90 headers=[('Content-Type', 'application/json')])
90 91 return response
91 92
92 93
@@ -470,6 +470,8 b' def get_repo_nodes(request, apiuser, rep'
470 470
471 471 ret_type = Optional.extract(ret_type)
472 472 details = Optional.extract(details)
473 max_file_bytes = Optional.extract(max_file_bytes)
474
473 475 _extended_types = ['basic', 'full']
474 476 if details not in _extended_types:
475 477 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
@@ -492,6 +494,7 b' def get_repo_nodes(request, apiuser, rep'
492 494 repo, revision, root_path, flat=False,
493 495 extended_info=extended_info, content=content,
494 496 max_file_bytes=max_file_bytes)
497
495 498 _map = {
496 499 'all': _d + _f,
497 500 'files': _f,
@@ -347,7 +347,7 b' def get_method(request, apiuser, pattern'
347 347
348 348 argspec = inspect.getargspec(func)
349 349 arglist = argspec[0]
350 defaults = map(repr, argspec[3] or [])
350 defaults = list(map(repr, argspec[3] or []))
351 351
352 352 default_empty = '<RequiredType>'
353 353
@@ -39,7 +39,7 b' def test(request, apiuser, args):'
39 39 @jsonrpc_method()
40 40 def test_ok(request, apiuser):
41 41 return {
42 'who': u'hello {} '.format(apiuser),
42 'who': f'hello {apiuser}',
43 43 'obj': {
44 44 'time': time.time(),
45 45 'dt': datetime.datetime.now(),
@@ -55,12 +55,15 b' def test_error(request, apiuser):'
55 55
56 56 @jsonrpc_method()
57 57 def test_exception(request, apiuser):
58 raise Exception('something unhanddled')
58 raise Exception('something unhandled')
59 59
60 60
61 61 @jsonrpc_method()
62 62 def test_params(request, apiuser, params):
63 return u'hello apiuser:{} params:{}'.format(apiuser, params)
63 return {
64 'who': f'hello {apiuser}',
65 'params': params
66 }
64 67
65 68
66 69 @jsonrpc_method()
@@ -69,16 +72,20 b' def test_params_opt('
69 72 opt3=Optional(OAttr('apiuser'))):
70 73 opt2 = Optional.extract(opt2)
71 74 opt3 = Optional.extract(opt3, evaluate_locals=locals())
72
73 return u'hello apiuser:{} params:{}, opt:[{},{},{}]'.format(
74 apiuser, params, opt1, opt2, opt3)
75 return {
76 'who': f'hello {apiuser}',
77 'params': params,
78 'opts': [
79 opt1, opt2, opt3
80 ]
81 }
75 82
76 83
77 84 @jsonrpc_method()
78 85 @jsonrpc_deprecated_method(
79 86 use_method='test_ok', deprecated_at_version='4.0.0')
80 87 def test_deprecated_method(request, apiuser):
81 return u'value'
88 return 'value'
82 89
83 90
84 91 @jsonrpc_method()
General Comments 0
You need to be logged in to leave comments. Login now