Browse code
doc
Ed L authored on 01/06/2011 20:29:56
Showing 6 changed files
Showing 6 changed files
- doc/source/conf.py
- jsonrpc/__main__.py
- jsonrpc/example_server.py
- jsonrpc/server.py
- jsonrpc/tests/test_jsonutil.py
- jsonrpc/tests/test_server.py
... | ... |
@@ -49,7 +49,7 @@ import sys, os |
49 | 49 |
# If extensions (or modules to document with autodoc) are in another directory, |
50 | 50 |
# add these directories to sys.path here. If the directory is relative to the |
51 | 51 |
# documentation root, use os.path.abspath to make it absolute, like shown here. |
52 |
-sys.path.insert(0, os.path.abspath('../../..')) |
|
52 |
+sys.path.insert(0, os.path.abspath('../..')) |
|
53 | 53 |
|
54 | 54 |
# -- General configuration ----------------------------------------------------- |
55 | 55 |
|
... | ... |
@@ -32,11 +32,14 @@ |
32 | 32 |
# |
33 | 33 |
from twisted.internet import reactor, ssl |
34 | 34 |
from twisted.web import server |
35 |
+import traceback |
|
35 | 36 |
|
36 |
-from .server import ServerEvents, JSON_RPC |
|
37 |
+from jsonrpc.server import ServerEvents, JSON_RPC |
|
37 | 38 |
|
38 | 39 |
class JSONRPCTest(ServerEvents): |
39 |
- def log(self, *a): print a |
|
40 |
+ def log(self, *a): |
|
41 |
+ print a |
|
42 |
+ |
|
40 | 43 |
def findmethod(self, method): |
41 | 44 |
if method in set(['add', 'subtract']): |
42 | 45 |
return getattr(self, method) |
... | ... |
@@ -32,12 +32,8 @@ |
32 | 32 |
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
33 | 33 |
# |
34 | 34 |
# |
35 |
-import cgi |
|
36 |
-import copy |
|
37 |
-import time |
|
38 |
-import itertools |
|
39 | 35 |
import jsonrpc.jsonutil |
40 |
-import json |
|
36 |
+from jsonrpc.utilities import public |
|
41 | 37 |
|
42 | 38 |
# Twisted imports |
43 | 39 |
from twisted.web import server |
... | ... |
@@ -48,19 +44,20 @@ from twisted.web.resource import Resource |
48 | 44 |
import UserDict, collections |
49 | 45 |
collections.Mapping.register(UserDict.DictMixin) |
50 | 46 |
|
47 |
+@public |
|
51 | 48 |
class ServerEvents(object): |
52 | 49 |
'''Subclass this and pass to :py:meth:`jsonrpc.customize` to customize the jsonrpc server''' |
53 | 50 |
|
54 |
- def __init__(self, jsonrpc): |
|
51 |
+ def __init__(self, server): |
|
55 | 52 |
#: A link to the JSON-RPC server instance |
56 |
- self.server = jsonrpc |
|
53 |
+ self.server = server |
|
57 | 54 |
|
58 |
- def callmethod(self, request, method, kwargs, args, **kw): |
|
55 |
+ def callmethod(self, txrequest, rpcrequest): |
|
59 | 56 |
'''Finds the method and calls it with the specified args''' |
60 |
- method = self.findmethod(method) |
|
57 |
+ method = self.findmethod(rpcrequest.method) |
|
61 | 58 |
if method is None: raise MethodNotFound |
62 | 59 |
|
63 |
- return method(*args, **kwargs) |
|
60 |
+ return method(*rpcrequest.args, **rpcrequest.kwargs) |
|
64 | 61 |
|
65 | 62 |
def findmethod(self, method): |
66 | 63 |
'''Override to allow server to define methods''' |
... | ... |
@@ -79,29 +76,90 @@ class ServerEvents(object): |
79 | 76 |
return content |
80 | 77 |
|
81 | 78 |
|
79 |
+@public |
|
82 | 80 |
class ServerError(Exception): |
81 |
+ '''Base Exception for JSON-RPC Errors, if this or a subclass of this is raised by a JSON-RPC method, |
|
82 |
+ The server will convert it into an appropriate error object |
|
83 |
+ ''' |
|
84 |
+ |
|
85 |
+ #: Error code |
|
83 | 86 |
code = 0 |
87 |
+ #: Error message |
|
84 | 88 |
msg = "" |
85 |
- id = None |
|
86 | 89 |
|
87 | 90 |
def json_equivalent(self): |
88 |
- return dict(jsonrpc="2.0", error=dict(code=self.code, message=self.msg), id=self.id) |
|
91 |
+ '''return a dictionary which matches an JSON-RPC Response''' |
|
92 |
+ return dict(code=self.code, message=self.msg) |
|
93 |
+ |
|
89 | 94 |
def __str__(self): |
90 | 95 |
return jsonrpc.jsonutil.encode(self) |
91 | 96 |
|
97 |
+@public |
|
92 | 98 |
class InvalidRequest(ServerError): |
99 |
+ '''Raise this when the Request object does not match the schema''' |
|
93 | 100 |
code = -32600 |
94 | 101 |
msg = "Invalid Request." |
95 | 102 |
|
103 |
+@public |
|
96 | 104 |
class MethodNotFound(ServerError): |
105 |
+ '''Raise this when the desired method is not found''' |
|
97 | 106 |
code = -32601 |
98 | 107 |
msg = "Procedure not found." |
99 | 108 |
|
109 |
+@public |
|
100 | 110 |
class ParseError(ServerError): |
111 |
+ '''Raise this when the request contains invalid JSON''' |
|
101 | 112 |
code = -32700 |
102 | 113 |
msg = "Parse error." |
103 | 114 |
|
115 |
+class Request(object): |
|
116 |
+ def __init__(self, content): |
|
117 |
+ self.version = content.get('jsonrpc') |
|
118 |
+ self.id = content.get('id') |
|
119 |
+ |
|
120 |
+ self.method = content.get('method') |
|
121 |
+ |
|
122 |
+ kwargs = content.get('params', {}) |
|
123 |
+ args = () |
|
124 |
+ if not isinstance(kwargs, dict): |
|
125 |
+ args = tuple(kwargs) |
|
126 |
+ kwargs = {} |
|
127 |
+ else: |
|
128 |
+ args = kwargs.pop('__args', args) |
|
129 |
+ |
|
130 |
+ self.args = args |
|
131 |
+ self.kwargs = dict( (str(k), v) for k,v in kwargs.items() ) |
|
132 |
+ |
|
133 |
+ def check(self): |
|
134 |
+ if self.version != '2.0': raise InvalidRequest |
|
135 |
+ if not isinstance(self.method, (str, unicode)): raise InvalidRequest |
|
136 |
+ |
|
137 |
+ @classmethod |
|
138 |
+ def from_list(cls, content): |
|
139 |
+ result = [] |
|
140 |
+ for req in content: |
|
141 |
+ result.append(cls(req)) |
|
142 |
+ return result |
|
143 |
+ |
|
144 |
+ |
|
145 |
+class Response(object): |
|
146 |
+ def __init__(self, id=None, result=None, error=None): |
|
147 |
+ self.version = '2.0' |
|
148 |
+ self.id = id |
|
149 |
+ self.result = result |
|
150 |
+ self.error = error |
|
151 |
+ |
|
152 |
+ def json_equivalent(self): |
|
153 |
+ res = dict(jsonrpc=self.version, id=self.id) |
|
154 |
+ if self.error is None: |
|
155 |
+ res['result'] = self.result |
|
156 |
+ else: |
|
157 |
+ res['error'] = self.error |
|
158 |
+ return res |
|
159 |
+ |
|
160 |
+ |
|
104 | 161 |
## Base class providing a JSON-RPC 2.0 implementation with 2 customizable hooks |
162 |
+@public |
|
105 | 163 |
class JSON_RPC(Resource): |
106 | 164 |
'''This class implements a JSON-RPC 2.0 server as a Twisted Resource''' |
107 | 165 |
isLeaf = True |
... | ... |
@@ -133,14 +191,58 @@ class JSON_RPC(Resource): |
133 | 191 |
|
134 | 192 |
content = self.eventhandler.processcontent(content, request) |
135 | 193 |
|
194 |
+ if isinstance(content, list): |
|
195 |
+ content = Request.from_list(content) |
|
196 |
+ else: |
|
197 |
+ content = Request(content) |
|
198 |
+ |
|
199 |
+ try: |
|
200 |
+ if hasattr(content, 'check'): |
|
201 |
+ content.check() |
|
202 |
+ else: |
|
203 |
+ for item in content: item.check() |
|
204 |
+ |
|
205 |
+ except ServerError, e: |
|
206 |
+ self._ebRender(e, request, content.id if hasattr(content, 'id') else None) |
|
207 |
+ |
|
136 | 208 |
d = threads.deferToThread(self._action, request, content) |
137 | 209 |
d.addCallback(self._cbRender, request) |
138 |
- d.addErrback(self._ebRender, request, content.get('id') if hasattr(content, 'get') else None) |
|
210 |
+ d.addErrback(self._ebRender, request, content.id if hasattr(content, 'id') else None) |
|
139 | 211 |
except BaseException, e: |
140 | 212 |
self._ebRender(e, request, None) |
141 | 213 |
|
142 | 214 |
return server.NOT_DONE_YET |
143 | 215 |
|
216 |
+ def _action(self, request, contents): |
|
217 |
+ result = [] |
|
218 |
+ |
|
219 |
+ islist = (True if isinstance(contents, list) else False) |
|
220 |
+ if not islist: contents = [contents] |
|
221 |
+ |
|
222 |
+ if contents == []: raise InvalidRequest |
|
223 |
+ |
|
224 |
+ for rpcrequest in contents: |
|
225 |
+ res = None |
|
226 |
+ |
|
227 |
+ try: |
|
228 |
+ res = Response(id=rpcrequest.id, result=self.eventhandler.callmethod(request, rpcrequest)) |
|
229 |
+ res = self.eventhandler.processrequest(res, request.args) |
|
230 |
+ except Exception, e: |
|
231 |
+ res = self.render_error(e, rpcrequest.id) |
|
232 |
+ |
|
233 |
+ if res.id is not None: |
|
234 |
+ result.append(res) |
|
235 |
+ |
|
236 |
+ |
|
237 |
+ self.eventhandler.log(result, request) |
|
238 |
+ |
|
239 |
+ if result != []: |
|
240 |
+ if not islist: result = result[0] |
|
241 |
+ else: result = None |
|
242 |
+ |
|
243 |
+ return result |
|
244 |
+ |
|
245 |
+ |
|
144 | 246 |
|
145 | 247 |
def _cbRender(self, result, request): |
146 | 248 |
if result is not None: |
... | ... |
@@ -169,69 +271,15 @@ class JSON_RPC(Resource): |
169 | 271 |
request.write(result) |
170 | 272 |
if finish: request.finish() |
171 | 273 |
|
172 |
- def _parse_data(self, content): |
|
173 |
- if content.get('jsonrpc') != '2.0': raise InvalidRequest |
|
174 |
- |
|
175 |
- method = content.get('method') |
|
176 |
- if not isinstance(method, (str, unicode)): raise InvalidRequest |
|
177 |
- |
|
178 |
- kwargs = content.get('params', {}) |
|
179 |
- args = () |
|
180 |
- if not isinstance(kwargs, dict): |
|
181 |
- args = tuple(kwargs) |
|
182 |
- kwargs = {} |
|
183 |
- else: |
|
184 |
- args = kwargs.pop('__args', args) |
|
185 |
- kwargs = dict( (str(k), v) for k,v in kwargs.items() ) |
|
186 |
- |
|
187 |
- return method, kwargs, args |
|
188 |
- |
|
189 |
- |
|
190 | 274 |
|
191 | 275 |
def render_error(self, e, id): |
192 | 276 |
if isinstance(e, ServerError): |
193 |
- e.id = id |
|
194 |
- err = e.json_equivalent() |
|
277 |
+ err = Response(id=id, error=e) |
|
195 | 278 |
else: |
196 |
- err = dict( |
|
197 |
- jsonrpc='2.0', |
|
198 |
- id = id, |
|
199 |
- error= dict( |
|
200 |
- code=0, |
|
201 |
- message=str(e), |
|
202 |
- data = e.args |
|
203 |
- )) |
|
279 |
+ err = Response(id=id, error=dict(code=0, message=str(e), data=e.args)) |
|
204 | 280 |
|
205 | 281 |
return err |
206 | 282 |
|
207 | 283 |
|
208 | 284 |
|
209 |
- def _action(self, request, contents): |
|
210 |
- result = [] |
|
211 |
- ol = (True if isinstance(contents, list) else False) |
|
212 |
- if not ol: contents = [contents] |
|
213 |
- |
|
214 |
- if contents == []: raise InvalidRequest |
|
215 |
- |
|
216 |
- for content in contents: |
|
217 |
- try: |
|
218 |
- res = dict(jsonrpc='2.0', id=content.get('id'), result=self.eventhandler.callmethod(request, *self._parse_data(content))) |
|
219 |
- |
|
220 |
- res = self.eventhandler.processrequest(res, request.args) |
|
221 |
- |
|
222 |
- if res['id'] is not None: result.append(res) |
|
223 |
- except Exception, e: |
|
224 |
- err = self.render_error(e, content.get('id')) |
|
225 |
- if err['id'] is not None: result.append(err) |
|
226 |
- |
|
227 |
- |
|
228 |
- |
|
229 |
- self.eventhandler.log(result, request) |
|
230 |
- |
|
231 |
- if result != []: |
|
232 |
- if not ol: result = result[0] |
|
233 |
- else: result = None |
|
234 |
- |
|
235 |
- return result |
|
236 |
- |
|
237 | 285 |
|
... | ... |
@@ -92,11 +92,10 @@ class TestJSONRPCServer(unittest.TestCase): |
92 | 92 |
|
93 | 93 |
@d.addCallback |
94 | 94 |
def rendered(ignored): |
95 |
- assert True |
|
96 |
- assert resource.eventhandler.processcontent.called |
|
97 |
- assert resource.eventhandler.callmethod.called |
|
98 |
- assert resource.eventhandler.processrequest.called |
|
99 |
- assert resource.eventhandler.log.called |
|
95 |
+ self.assertTrue(resource.eventhandler.processcontent.called) |
|
96 |
+ self.assertTrue(resource.eventhandler.callmethod.called) |
|
97 |
+ self.assertTrue(resource.eventhandler.processrequest.called) |
|
98 |
+ self.assertTrue(resource.eventhandler.log.called) |
|
100 | 99 |
|
101 | 100 |
return d |
102 | 101 |
|
... | ... |
@@ -113,10 +112,10 @@ class TestJSONRPCServer(unittest.TestCase): |
113 | 112 |
|
114 | 113 |
@d.addCallback |
115 | 114 |
def rendered(ignored): |
116 |
- assert len(request.written) == 1 |
|
115 |
+ self.assertEqual(len(request.written), 1) |
|
117 | 116 |
data = jsonrpc.jsonutil.decode(request.written[0]) |
118 | 117 |
|
119 |
- assert data == {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error."}, "id": None} |
|
118 |
+ self.assertEqual(data, {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error."}, "id": None}) |
|
120 | 119 |
|
121 | 120 |
return d |
122 | 121 |
|
... | ... |
@@ -132,9 +131,9 @@ class TestJSONRPCServer(unittest.TestCase): |
132 | 131 |
|
133 | 132 |
@d.addCallback |
134 | 133 |
def rendered(ignored): |
135 |
- assert len(request.written) == 1 |
|
134 |
+ self.assertEqual(len(request.written), 1) |
|
136 | 135 |
data = jsonrpc.jsonutil.decode(request.written[0]) |
137 |
- assert data == {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": self.id_} |
|
136 |
+ self.assertEqual(data, {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": self.id_}) |
|
138 | 137 |
return d |
139 | 138 |
|
140 | 139 |
|
... | ... |
@@ -150,9 +149,9 @@ class TestJSONRPCServer(unittest.TestCase): |
150 | 149 |
|
151 | 150 |
@d.addCallback |
152 | 151 |
def rendered(ignored): |
153 |
- assert len(request.written) == 1 |
|
152 |
+ self.assertEqual(len(request.written), 1) |
|
154 | 153 |
data = jsonrpc.jsonutil.decode(request.written[0]) |
155 |
- assert data == {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": self.id_} |
|
154 |
+ self.assertEqual(data, {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": self.id_}) |
|
156 | 155 |
return d |
157 | 156 |
|
158 | 157 |
def test_missingmethod(self): |
... | ... |
@@ -167,9 +166,9 @@ class TestJSONRPCServer(unittest.TestCase): |
167 | 166 |
|
168 | 167 |
@d.addCallback |
169 | 168 |
def rendered(ignored): |
170 |
- assert len(request.written) == 1 |
|
169 |
+ self.assertEqual(len(request.written), 1) |
|
171 | 170 |
data = jsonrpc.jsonutil.decode(request.written[0]) |
172 |
- assert data == {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Procedure not found."}, "id": self.id_} |
|
171 |
+ self.assertEqual(data, {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Procedure not found."}, "id": self.id_}) |
|
173 | 172 |
return d |
174 | 173 |
|
175 | 174 |
|
... | ... |
@@ -186,11 +185,11 @@ class TestJSONRPCServer(unittest.TestCase): |
186 | 185 |
|
187 | 186 |
@d.addCallback |
188 | 187 |
def rendered(ignored): |
189 |
- assert len(request.written) == 1 |
|
188 |
+ self.assertEqual(len(request.written), 1) |
|
190 | 189 |
data = jsonrpc.jsonutil.decode(request.written[0]) |
191 | 190 |
|
192 |
- assert data['id'] == self.id_ |
|
193 |
- assert data['result'] == self.param |
|
191 |
+ self.assertEqual(data['id'], self.id_) |
|
192 |
+ self.assertEqual(data['result'], self.param) |
|
194 | 193 |
return d |
195 | 194 |
|
196 | 195 |
def test_notify(self): |
... | ... |
@@ -205,7 +204,7 @@ class TestJSONRPCServer(unittest.TestCase): |
205 | 204 |
|
206 | 205 |
@d.addCallback |
207 | 206 |
def rendered(ignored): |
208 |
- assert len(request.written) == 0 |
|
207 |
+ self.assertEqual(len(request.written), 0) |
|
209 | 208 |
|
210 | 209 |
return d |
211 | 210 |
|
... | ... |
@@ -222,11 +221,11 @@ class TestJSONRPCServer(unittest.TestCase): |
222 | 221 |
|
223 | 222 |
@d.addCallback |
224 | 223 |
def rendered(ignored): |
225 |
- assert len(request.written) == 1 |
|
224 |
+ self.assertEqual(len(request.written), 1) |
|
226 | 225 |
data = jsonrpc.jsonutil.decode(request.written[0]) |
227 | 226 |
|
228 |
- assert data['id'] == self.id_ |
|
229 |
- assert data['result'] == self.param |
|
227 |
+ self.assertEqual(data['id'], self.id_) |
|
228 |
+ self.assertEqual(data['result'], self.param) |
|
230 | 229 |
|
231 | 230 |
return d |
232 | 231 |
|
... | ... |
@@ -243,11 +242,11 @@ class TestJSONRPCServer(unittest.TestCase): |
243 | 242 |
|
244 | 243 |
@d.addCallback |
245 | 244 |
def rendered(ignored, *a): |
246 |
- assert len(request.written) == 1 |
|
245 |
+ self.assertEqual(len(request.written), 1) |
|
247 | 246 |
data = jsonrpc.jsonutil.decode(request.written[0]) |
248 | 247 |
|
249 |
- assert data['id'] == self.id_ |
|
250 |
- assert data.get('error', False) |
|
248 |
+ self.assertEqual(data['id'], self.id_) |
|
249 |
+ self.assertTrue(data.get('error', False)) |
|
251 | 250 |
return rendered |
252 | 251 |
|
253 | 252 |
def test_batchcall(self): |
... | ... |
@@ -265,13 +264,13 @@ class TestJSONRPCServer(unittest.TestCase): |
265 | 264 |
|
266 | 265 |
@d.addCallback |
267 | 266 |
def rendered(ignored, *a): |
268 |
- assert len(request.written) == 1 |
|
267 |
+ self.assertEqual(len(request.written), 1) |
|
269 | 268 |
data = jsonrpc.jsonutil.decode(request.written[0]) |
270 |
- assert len(data) == 2 |
|
271 |
- assert set(x['id'] for x in data) == set("12") |
|
272 |
- assert set(x['result'] for x in data) == set([3,5]) |
|
269 |
+ self.assertEqual(len(data), 2) |
|
270 |
+ self.assertEqual(set(x['id'] for x in data), set("12")) |
|
271 |
+ self.assertEqual(set(x['result'] for x in data), set([3,5])) |
|
273 | 272 |
|
274 |
- assert not any(x.get('error', False) for x in data) |
|
273 |
+ self.assertFalse(any(x.get('error', False) for x in data)) |
|
275 | 274 |
return rendered |
276 | 275 |
|
277 | 276 |
def test_batchcall_1err(self): |
... | ... |
@@ -289,13 +288,13 @@ class TestJSONRPCServer(unittest.TestCase): |
289 | 288 |
|
290 | 289 |
@d.addCallback |
291 | 290 |
def rendered(ignored, *a): |
292 |
- assert len(request.written) == 1 |
|
291 |
+ self.assertEqual(len(request.written), 1) |
|
293 | 292 |
data = jsonrpc.jsonutil.decode(request.written[0]) |
294 |
- assert len(data) == 2 |
|
295 |
- assert set(x['id'] for x in data) == set("12") |
|
296 |
- assert set(x.get('result', False) for x in data) == set([3,False]) |
|
293 |
+ self.assertEqual(len(data), 2) |
|
294 |
+ self.assertEqual(set(x['id'] for x in data), set("12")) |
|
295 |
+ self.assertEqual(set(x.get('result', False) for x in data), set([3,False])) |
|
297 | 296 |
|
298 |
- assert len(filter(None, [x.get('error') for x in data])) == 1 |
|
297 |
+ self.assertEqual(len(filter(None, [x.get('error') for x in data])), 1) |
|
299 | 298 |
return rendered |
300 | 299 |
|
301 | 300 |
|
... | ... |
@@ -313,7 +312,7 @@ class TestJSONRPCServer(unittest.TestCase): |
313 | 312 |
@d.addCallback |
314 | 313 |
def rendered(ignored, *a): |
315 | 314 |
data = jsonrpc.jsonutil.decode(request.written[0]) |
316 |
- assert data == {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": None} |
|
315 |
+ self.assertEqual(data, {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": None}) |
|
317 | 316 |
return rendered |
318 | 317 |
|
319 | 318 |
|