annotate wibble/server/__init__.py @ 4:ad5a8748afcf

Add test framework
author Ben Croston <ben@croston.org>
date Wed, 31 Aug 2011 21:35:14 +0100
parents server/JsonRpcApp.py@c7a236de5214
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
4
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
1 #!/usr/bin/env python
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
2
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
3 # Copyright (c) 2011 Ben Croston
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
4 #
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
5 # Permission is hereby granted, free of charge, to any person obtaining a copy of
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
6 # this software and associated documentation files (the "Software"), to deal in
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
7 # the Software without restriction, including without limitation the rights to
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
8 # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
9 # of the Software, and to permit persons to whom the Software is furnished to do
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
10 # so, subject to the following conditions:
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
11 #
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
12 # The above copyright notice and this permission notice shall be included in all
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
13 # copies or substantial portions of the Software.
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
14 #
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
21 # SOFTWARE.
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
22
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
23 from json import loads, dumps
0
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
24 import traceback
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
25 import sys
4
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
26 from webob import Request, Response, exc
0
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
27
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
28 class JsonRpcApp(object):
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
29 """
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
30 Serve the given object via json-rpc (http://json-rpc.org/)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
31 """
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
32
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
33 def __init__(self, obj, auth=None):
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
34 """
4
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
35 obj - a class containing functions available using jsonrpc
0
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
36 auth - an authentication function (optional)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
37 """
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
38 self.obj = obj
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
39 self.auth = auth
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
40
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
41 def __call__(self, environ, start_response):
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
42 req = Request(environ)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
43 try:
4
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
44 resp = self._process(req)
0
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
45 except ValueError, e:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
46 resp = exc.HTTPBadRequest(str(e))
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
47 except exc.HTTPException, e:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
48 resp = e
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
49 return resp(environ, start_response)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
50
4
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
51 def _process(self, req):
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
52 """
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
53 Process the JSONRPC request.
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
54 req - a webob Request object
ad5a8748afcf Add test framework
Ben Croston <ben@croston.org>
parents: 2
diff changeset
55 """
0
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
56 if not req.method == 'POST':
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
57 raise exc.HTTPMethodNotAllowed("Only POST allowed").exception
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
58
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
59 try:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
60 json = loads(req.body)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
61 except ValueError, e:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
62 raise ValueError('Bad JSON: %s' % e)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
63
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
64 try:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
65 method = json['method']
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
66 params = json['params']
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
67 id = json['id']
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
68 username = json['username'] if 'username' in json else None
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
69 password = json['password'] if 'password' in json else None
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
70 except KeyError, e:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
71 raise ValueError("JSON body missing parameter: %s" % e)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
72
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
73 if params is None:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
74 params = []
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
75 if not isinstance(params, list):
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
76 raise ValueError("Bad params %r: must be a list" % params)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
77 text = traceback.format_exc()
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
78 exc_value = sys.exc_info()[1]
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
79 error_value = dict(
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
80 name='JSONRPCError',
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
81 code=100,
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
82 message=str(exc_value),
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
83 error=text)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
84 return Response(
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
85 status=500,
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
86 content_type='application/json',
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
87 body=dumps(dict(result=None,
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
88 error=error_value,
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
89 id=id)))
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
90
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
91 obj = self.obj
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
92 if isinstance(self.obj,tuple) or isinstance(self.obj,list):
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
93 for x in self.obj:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
94 if method.startswith('%s.'%x.__class__.__name__):
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
95 obj = x
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
96 method = method.replace('%s.'%obj.__class__.__name__,'',1)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
97 break
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
98 elif method.startswith('%s.'%self.obj.__class__.__name__):
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
99 method = method.replace('%s.'%self.obj.__class__.__name__,'',1)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
100 if method.startswith('_'):
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
101 raise exc.HTTPForbidden("Bad method name %s: must not start with _" % method).exception
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
102 try:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
103 method = getattr(obj, method)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
104 except AttributeError:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
105 raise ValueError("No such method %s" % method)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
106
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
107 if self.auth is not None:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
108 try:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
109 auth_result = self.auth(username, password, req.user_agent)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
110 except:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
111 text = traceback.format_exc()
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
112 exc_value = sys.exc_info()[1]
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
113 error_value = dict(
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
114 name='JSONRPCError',
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
115 code=100,
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
116 message=str(exc_value),
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
117 error=text)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
118 return Response(
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
119 status=500,
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
120 content_type='application/json',
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
121 body=dumps(dict(result=None,
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
122 error=error_value,
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
123 id=id)))
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
124 if not auth_result:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
125 raise exc.HTTPUnauthorized().exception
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
126
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
127 try:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
128 result = method(*params)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
129 except:
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
130 text = traceback.format_exc()
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
131 exc_value = sys.exc_info()[1]
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
132 error_value = dict(
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
133 name='JSONRPCError',
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
134 code=100,
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
135 message=str(exc_value),
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
136 error=text)
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
137 return Response(
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
138 status=500,
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
139 content_type='application/json',
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
140 body=dumps(dict(result=None,
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
141 error=error_value,
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
142 id=id)))
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
143
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
144 return Response(
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
145 content_type='application/json',
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
146 body=dumps(dict(result=result,
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
147 error=None,
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
148 id=id)))
6a61cfdf6930 Initial version
Ben Croston <ben@croston.org>
parents:
diff changeset
149