##// END OF EJS Templates
Added optional arguments into API
marcink -
r1504:ed3254ac beta
parent child Browse files
Show More
@@ -1,229 +1,239 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.api
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 JSON RPC controller
7 7
8 8 :created_on: Aug 20, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import inspect
29 29 import json
30 30 import logging
31 31 import types
32 32 import urllib
33 from itertools import izip_longest
33 34
34 35 from paste.response import replace_header
35 36
36 37 from pylons.controllers import WSGIController
37 38 from pylons.controllers.util import Response
38 39
39 40 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
40 41 HTTPBadRequest, HTTPError
41 42
42 43 from rhodecode.model.db import User
43 44 from rhodecode.lib.auth import AuthUser
44 45
45 46 log = logging.getLogger('JSONRPC')
46 47
47 48 class JSONRPCError(BaseException):
48 49
49 50 def __init__(self, message):
50 51 self.message = message
51 52
52 53 def __str__(self):
53 54 return str(self.message)
54 55
55 56
56 57 def jsonrpc_error(message, code=None):
57 58 """Generate a Response object with a JSON-RPC error body"""
58 59 return Response(body=json.dumps(dict(result=None,
59 60 error=message)))
60 61
61 62
62 63 class JSONRPCController(WSGIController):
63 64 """
64 65 A WSGI-speaking JSON-RPC controller class
65 66
66 67 See the specification:
67 68 <http://json-rpc.org/wiki/specification>`.
68 69
69 70 Valid controller return values should be json-serializable objects.
70 71
71 72 Sub-classes should catch their exceptions and raise JSONRPCError
72 73 if they want to pass meaningful errors to the client.
73 74
74 75 """
75 76
76 77 def _get_method_args(self):
77 78 """
78 79 Return `self._rpc_args` to dispatched controller method
79 80 chosen by __call__
80 81 """
81 82 return self._rpc_args
82 83
83 84 def __call__(self, environ, start_response):
84 85 """
85 86 Parse the request body as JSON, look up the method on the
86 87 controller and if it exists, dispatch to it.
87 88 """
88 89 if 'CONTENT_LENGTH' not in environ:
89 90 log.debug("No Content-Length")
90 91 return jsonrpc_error(message="No Content-Length in request")
91 92 else:
92 93 length = environ['CONTENT_LENGTH'] or 0
93 94 length = int(environ['CONTENT_LENGTH'])
94 95 log.debug('Content-Length: %s', length)
95 96
96 97 if length == 0:
97 98 log.debug("Content-Length is 0")
98 99 return jsonrpc_error(message="Content-Length is 0")
99 100
100 101 raw_body = environ['wsgi.input'].read(length)
101 102
102 103 try:
103 104 json_body = json.loads(urllib.unquote_plus(raw_body))
104 105 except ValueError as e:
105 106 #catch JSON errors Here
106 107 return jsonrpc_error(message="JSON parse error ERR:%s RAW:%r" \
107 108 % (e, urllib.unquote_plus(raw_body)))
108 109
109 110 #check AUTH based on API KEY
110 111 try:
111 112 self._req_api_key = json_body['api_key']
112 113 self._req_method = json_body['method']
113 114 self._req_params = json_body['args']
114 115 log.debug('method: %s, params: %s',
115 116 self._req_method,
116 117 self._req_params)
117 118 except KeyError as e:
118 119 return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
119 120
120 121 #check if we can find this session using api_key
121 122 try:
122 123 u = User.get_by_api_key(self._req_api_key)
123 124 auth_u = AuthUser(u.user_id, self._req_api_key)
124 125 except Exception as e:
125 126 return jsonrpc_error(message='Invalid API KEY')
126 127
127 128 self._error = None
128 129 try:
129 130 self._func = self._find_method()
130 131 except AttributeError, e:
131 132 return jsonrpc_error(message=str(e))
132 133
133 134 # now that we have a method, add self._req_params to
134 135 # self.kargs and dispatch control to WGIController
135 arglist = inspect.getargspec(self._func)[0][1:]
136 argspec = inspect.getargspec(self._func)
137 arglist = argspec[0][1:]
138 defaults = argspec[3]
139 default_empty = types.NotImplementedType
140 kwarglist = list(izip_longest(reversed(arglist),reversed(defaults),
141 fillvalue=default_empty))
136 142
137 143 # this is little trick to inject logged in user for
138 144 # perms decorators to work they expect the controller class to have
139 145 # rhodecode_user attribute set
140 146 self.rhodecode_user = auth_u
141 147
142 148 # This attribute will need to be first param of a method that uses
143 149 # api_key, which is translated to instance of user at that name
144 150 USER_SESSION_ATTR = 'apiuser'
145 151
146 152 if USER_SESSION_ATTR not in arglist:
147 153 return jsonrpc_error(message='This method [%s] does not support '
148 154 'authentication (missing %s param)' %
149 155 (self._func.__name__, USER_SESSION_ATTR))
150 156
151 157 # get our arglist and check if we provided them as args
152 for arg in arglist:
158 for arg,default in kwarglist:
153 159 if arg == USER_SESSION_ATTR:
154 160 # USER_SESSION_ATTR is something translated from api key and
155 161 # this is checked before so we don't need validate it
156 162 continue
157
158 if not self._req_params or arg not in self._req_params:
159 return jsonrpc_error(message='Missing %s arg in JSON DATA' % arg)
163
164 # skip the required param check if it's default value is
165 # NotImplementedType (default_empty)
166 if not self._req_params or (type(default) == default_empty
167 and arg not in self._req_params):
168 return jsonrpc_error(message=('Missing non optional %s arg '
169 'in JSON DATA') % arg)
160 170
161 171 self._rpc_args = {USER_SESSION_ATTR:u}
162 172 self._rpc_args.update(self._req_params)
163 173
164 174 self._rpc_args['action'] = self._req_method
165 175 self._rpc_args['environ'] = environ
166 176 self._rpc_args['start_response'] = start_response
167 177
168 178 status = []
169 179 headers = []
170 180 exc_info = []
171 181 def change_content(new_status, new_headers, new_exc_info=None):
172 182 status.append(new_status)
173 183 headers.extend(new_headers)
174 184 exc_info.append(new_exc_info)
175 185
176 186 output = WSGIController.__call__(self, environ, change_content)
177 187 output = list(output)
178 188 headers.append(('Content-Length', str(len(output[0]))))
179 189 replace_header(headers, 'Content-Type', 'application/json')
180 190 start_response(status[0], headers, exc_info[0])
181 191
182 192 return output
183 193
184 194 def _dispatch_call(self):
185 195 """
186 196 Implement dispatch interface specified by WSGIController
187 197 """
188 198 try:
189 199 raw_response = self._inspect_call(self._func)
190 200 if isinstance(raw_response, HTTPError):
191 201 self._error = str(raw_response)
192 202 except JSONRPCError as e:
193 203 self._error = str(e)
194 204 except Exception as e:
195 205 log.debug('Encountered unhandled exception: %s', repr(e))
196 206 json_exc = JSONRPCError('Internal server error')
197 207 self._error = str(json_exc)
198 208
199 209 if self._error is not None:
200 210 raw_response = None
201 211
202 212 response = dict(result=raw_response, error=self._error)
203 213
204 214 try:
205 215 return json.dumps(response)
206 216 except TypeError, e:
207 217 log.debug('Error encoding response: %s', e)
208 218 return json.dumps(dict(result=None,
209 219 error="Error encoding response"))
210 220
211 221 def _find_method(self):
212 222 """
213 223 Return method named by `self._req_method` in controller if able
214 224 """
215 225 log.debug('Trying to find JSON-RPC method: %s', self._req_method)
216 226 if self._req_method.startswith('_'):
217 227 raise AttributeError("Method not allowed")
218 228
219 229 try:
220 230 func = getattr(self, self._req_method, None)
221 231 except UnicodeEncodeError:
222 232 raise AttributeError("Problem decoding unicode in requested "
223 233 "method name.")
224 234
225 235 if isinstance(func, types.MethodType):
226 236 return func
227 237 else:
228 238 raise AttributeError("No such method: %s" % self._req_method)
229 239
General Comments 0
You need to be logged in to leave comments. Login now