git.fiddlerwoaroof.com
Browse code

refactoring: removing code duplicated between server and client

Ed L authored on 03/06/2011 02:14:42
Showing 6 changed files
... ...
@@ -40,15 +40,13 @@ add and subtract:
40 40
    :tab-width: 2
41 41
    :lines: 2,3,34-
42 42
 
43
-To use the server, start the client this way:
43
+To use this server (which is included as jsonrpc.example_server), start it and the client in this way:
44 44
 
45
-.. code-block:: bash
45
+.. code-block:: console
46 46
 
47
-   # Python 2.6
48
-   % python2.6 -i -m jsonrpc.__main__ http://localhost:8007
47
+   % python -m jsonrpc.example_server &
49 48
 
50
-   # Python 2.7
51
-   % python2.7 -i -m jsonrpc http://localhost:8007
49
+   % python -i -m jsonrpc.__main__ http://localhost:8007
52 50
    
53 51
 .. code-block:: python
54 52
 
... ...
@@ -47,7 +47,7 @@ Contents:
47 47
    getting_started
48 48
    server
49 49
    proxy
50
-   jsonutil
50
+.. jsonutil
51 51
 
52 52
 Indices and tables
53 53
 ==================
54 54
new file mode 100644
... ...
@@ -0,0 +1,187 @@
1
+#
2
+#  Copyright (c) 2011 Edward Langley
3
+#  All rights reserved.
4
+#
5
+#  Redistribution and use in source and binary forms, with or without
6
+#  modification, are permitted provided that the following conditions
7
+#  are met:
8
+#
9
+#  Redistributions of source code must retain the above copyright notice,
10
+#  this list of conditions and the following disclaimer.
11
+#
12
+#  Redistributions in binary form must reproduce the above copyright
13
+#  notice, this list of conditions and the following disclaimer in the
14
+#  documentation and/or other materials provided with the distribution.
15
+#
16
+#  Neither the name of the project's author nor the names of its
17
+#  contributors may be used to endorse or promote products derived from
18
+#  this software without specific prior written permission.
19
+#
20
+#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
+#  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
+#  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23
+#  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24
+#  HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
+#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
26
+#  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27
+#  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28
+#  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29
+#  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30
+#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+#
32
+#
33
+from jsonrpc.utilities import public
34
+import jsonrpc.jsonutil
35
+
36
+@public
37
+class RPCError(Exception):
38
+	'''Base Exception for JSON-RPC Errors, if this or a subclass of this is raised by a JSON-RPC method,
39
+	The server will convert it into an appropriate error object
40
+	'''
41
+
42
+	#: Error code
43
+	code = 0
44
+	#: Error message
45
+	msg = ""
46
+
47
+	@classmethod
48
+	def from_dict(cls, err):
49
+		self = cls()
50
+		self.code = err['code']
51
+		self.msg = err['message']
52
+		return self
53
+
54
+	def json_equivalent(self):
55
+		'''return a dictionary which matches an JSON-RPC Response'''
56
+		return dict(code=self.code, message=self.msg)
57
+
58
+	def __str__(self):
59
+		return jsonrpc.jsonutil.encode(self)
60
+
61
+@public
62
+class InvalidRequest(RPCError):
63
+	'''Raise this when the Request object does not match the schema'''
64
+	code = -32600
65
+	msg = "Invalid Request."
66
+
67
+@public
68
+class MethodNotFound(RPCError):
69
+	'''Raise this when the desired method is not found'''
70
+	code = -32601
71
+	msg = "Procedure not found."
72
+
73
+@public
74
+class ParseError(RPCError):
75
+	'''Raise this when the request contains invalid JSON'''
76
+	code = -32700
77
+	msg = "Parse error."
78
+
79
+codemap = {0: RPCError}
80
+codemap.update( (e.code, e) for e in RPCError.__subclasses__() )
81
+
82
+class JsonInstantiate:
83
+   @classmethod
84
+   def from_json(cls, json):
85
+      data = jsonrpc.jsonutil.decode(json)
86
+      if isinstance(data, list):
87
+         result = cls.from_list(data)
88
+      else:
89
+         result = cls.from_dict(data)
90
+      return result
91
+
92
+   @classmethod
93
+   def from_list(cls, responses):
94
+      return [cls.from_dict(r) for r in responses]
95
+
96
+
97
+class Request(object, JsonInstantiate):
98
+	def __init__(self, id, method, args=None, kwargs=None, extra=None, version='2.0'):
99
+		self.version = version
100
+		self.id = id
101
+		self.method = method
102
+		self.args = args
103
+		self.kwargs = kwargs
104
+		self.extra = extra or {}
105
+
106
+	@classmethod
107
+	def from_dict(cls, content):
108
+		version = content.pop('jsonrpc', None)
109
+		id = content.pop('id', None)
110
+
111
+		method = content.pop('method', None)
112
+
113
+		kwargs = content.pop('params', {})
114
+		args = ()
115
+		if not isinstance(kwargs, dict):
116
+			args = tuple(kwargs)
117
+			kwargs = {}
118
+		else:
119
+			args = kwargs.pop('__args', args)
120
+
121
+		args = args
122
+		kwargs = dict( (str(k), v) for k,v in kwargs.items() )
123
+		extra = content
124
+		return cls(id, method, args, kwargs, extra, version)
125
+
126
+
127
+	def check(self):
128
+		if self.version != '2.0': raise InvalidRequest
129
+		if not isinstance(self.method, (str, unicode)): raise InvalidRequest
130
+
131
+
132
+	def json_equivalent(self):
133
+		if self.kwargs.has_key('__args'):
134
+			raise ValueError, 'invalid argument name: __args'
135
+
136
+		params = self.args
137
+		if self.args and self.kwargs:
138
+			self.kwargs['__args'] = self.args
139
+		if self.kwargs:
140
+			result['params'] = self.kwargs
141
+
142
+		return dict(
143
+			jsonrpc = self.version,
144
+			id = self.id,
145
+			method = self.method,
146
+			params = params
147
+		)
148
+
149
+
150
+
151
+class Response(object, JsonInstantiate):
152
+	def __init__(self, id=None, result=None, error=None, version='2.0'):
153
+		self.version = version
154
+		self.id = id
155
+		self.result = result
156
+		self.error = error
157
+
158
+
159
+	@classmethod
160
+	def from_dict(cls, response):
161
+		version = response.get('jsonrpc', None)
162
+		id = response['id']
163
+		result = response.get('result', None)
164
+		error = response.get('error', None)
165
+
166
+		return cls(id, result, error, version)
167
+
168
+	def json_equivalent(self):
169
+		res = dict(jsonrpc=self.version, id=self.id)
170
+		if self.error is None:
171
+			res['result'] = self.result
172
+		else:
173
+			res['error'] = self.error
174
+		return res
175
+
176
+	def get_result(self):
177
+		'''get result and raise any errors'''
178
+		if self.error:
179
+			code = self.error['code']
180
+			raise codemap.get(code, RPCError).from_dict(self.error)
181
+		return self.result
182
+
183
+	def get_output(self):
184
+		'''get tuple (result, error)'''
185
+		return self.result, self.error
186
+
187
+
... ...
@@ -42,10 +42,10 @@ class ExampleServer(ServerEvents):
42 42
 	def log(self, responses, txrequest):
43 43
 		if isinstance(responses, list):
44 44
 			for response in responses:
45
-				msg = self.get_msg(response)
45
+				msg = self._get_msg(response)
46 46
 				print txrequest, msg
47 47
 		else:
48
-			msg = self.get_msg(response)
48
+			msg = self._get_msg(responses)
49 49
 			print txrequest, msg
50 50
 
51 51
 	def findmethod(self, method):
... ...
@@ -57,7 +57,7 @@ class ExampleServer(ServerEvents):
57 57
 	# helper methods
58 58
 	methods = set(['add', 'subtract'])
59 59
 	def _get_msg(self, response):
60
-		return ' '.join([response.id, response.result or response.error])
60
+		return ' '.join(str(x) for x in [response.id, response.result or response.error])
61 61
 
62 62
 	def subtract(self, a, b):
63 63
 		return a-b
... ...
@@ -65,7 +65,7 @@ class ExampleServer(ServerEvents):
65 65
 	def add(self, a, b):
66 66
 		return a+b
67 67
 
68
-root = JSON_RPC().customize(JSONRPCTest)
68
+root = JSON_RPC().customize(ExampleServer)
69 69
 site = server.Site(root)
70 70
 
71 71
 
... ...
@@ -1,24 +1,24 @@
1 1
 # $Id: proxy.py,v 1.20 2011/05/26 20:19:17 edwlan Exp $
2 2
 
3
-#  
3
+#
4 4
 #  Copyright (c) 2011 Edward Langley
5 5
 #  All rights reserved.
6
-#  
6
+#
7 7
 #  Redistribution and use in source and binary forms, with or without
8 8
 #  modification, are permitted provided that the following conditions
9 9
 #  are met:
10
-#  
10
+#
11 11
 #  Redistributions of source code must retain the above copyright notice,
12 12
 #  this list of conditions and the following disclaimer.
13
-#  
13
+#
14 14
 #  Redistributions in binary form must reproduce the above copyright
15 15
 #  notice, this list of conditions and the following disclaimer in the
16 16
 #  documentation and/or other materials provided with the distribution.
17
-#  
17
+#
18 18
 #  Neither the name of the project's author nor the names of its
19 19
 #  contributors may be used to endorse or promote products derived from
20 20
 #  this software without specific prior written permission.
21
-#  
21
+#
22 22
 #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 23
 #  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 24
 #  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
... ...
@@ -30,7 +30,7 @@
30 30
 #  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
31 31
 #  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32 32
 #  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
-#  
33
+#
34 34
 #
35 35
 import copy
36 36
 import urllib
... ...
@@ -44,6 +44,7 @@ collections.Mapping.register(UserDict.DictMixin)
44 44
 
45 45
 from hashlib import sha1
46 46
 import jsonrpc.jsonutil
47
+from jsonrpc.common import Response, Request
47 48
 
48 49
 __all__ = ['JSONRPCProxy', 'ProxyEvents']
49 50
 
... ...
@@ -56,13 +57,6 @@ class NewStyleBaseException(Exception):
56 57
     message = property(_get_message, _set_message)
57 58
 
58 59
 
59
-class JSONRPCException(NewStyleBaseException):
60
-	def __init__(self, rpcError):
61
-		Exception.__init__(self, rpcError.get('message'))
62
-		self.data = rpcError.get('data')
63
-		self.message = rpcError.get('message')
64
-		self.code = rpcError.get('code')
65
-
66 60
 class IDGen(object):
67 61
 	def __init__(self):
68 62
 		self._hasher = sha1()
... ...
@@ -98,33 +92,6 @@ class ProxyEvents(object):
98 92
 		'''allow a subclass to access the response data before it is returned to the user'''
99 93
 		return data
100 94
 
101
-
102
-class Request(object):
103
-
104
-	def __init__(self, id, method, args=None, kwargs=None):
105
-		self.version = '2.0'
106
-		self.id = id
107
-		self.method = method
108
-		self.args = args
109
-		self.kwargs = kwargs
110
-
111
-	def json_equivalent(self):
112
-		if kwargs.has_key('__args'):
113
-			raise ValueError, 'invalid argument name: __args'
114
-
115
-		result = dict(
116
-			jsonrpc = self.version,
117
-			id = self.id,
118
-			method = self.method
119
-		)
120
-
121
-		if self.args and self.kwargs:
122
-			self.kwargs['__args'] = self.args
123
-
124
-
125
-
126
-
127
-
128 95
 class JSONRPCProxy(object):
129 96
 	'''A class implementing a JSON-RPC Proxy.
130 97
 
... ...
@@ -182,9 +149,8 @@ class JSONRPCProxy(object):
182 149
 	def _get_postdata(self, args=None, kwargs=None):
183 150
 		args,kwargs = self._eventhandler.get_postdata(args, kwargs)
184 151
 		id = self._eventhandler.IDGen
185
-		return Request(id, self._serviceNams, args, kwargs)
186
-		postdata = jsonrpc.jsonutil.encode({	})
187
-		return postdata
152
+		result = Request(id, self._serviceName, args, kwargs)
153
+		return jsonrpc.jsonutil.encode(result)
188 154
 
189 155
 
190 156
 	def __call__(self, *args, **kwargs):
... ...
@@ -192,14 +158,10 @@ class JSONRPCProxy(object):
192 158
 		url = '%(host)s%(path)s' % dict(host = self.serviceURL, path = self._path)
193 159
 		postdata = self._get_postdata(args, kwargs)
194 160
 		respdata = urllib.urlopen(url, postdata).read()
195
-		resp = jsonrpc.jsonutil.decode(respdata)
161
+		resp = Response.from_dict(jsonrpc.jsonutil.decode(respdata))
162
+		resp = self._eventhandler.proc_response(resp)
196 163
 
197
-		if resp.get('error') != None:
198
-			raise JSONRPCException(resp['error'])
199
-		else:
200
-			resp = self._eventhandler.proc_response(resp)
201
-			result = resp['result']
202
-			return result
164
+		return resp.get_result()
203 165
 
204 166
 
205 167
 	def call(self, method, *args, **kwargs):
... ...
@@ -218,9 +180,15 @@ class JSONRPCProxy(object):
218 180
 		:param names: a dictionary { method: (args, kwargs) }
219 181
 		:returns: a list of pairs (result, error) where only one is not None
220 182
 		'''
183
+		result = None
221 184
 		if hasattr(methods, 'items'): methods = methods.items()
222 185
 		data = [ getattr(self, k)._get_postdata(*v) for k, v in methods ]
223 186
 		postdata = '[%s]' % ','.join(data)
224 187
 		respdata = urllib.urlopen(self.serviceURL, postdata).read()
225
-		resp = jsonrpc.jsonutil.decode(respdata)
226
-		return [(res.get('result'), res.get('error')) for res in resp]
188
+		resp = Response.from_json(respdata)
189
+		try:
190
+			result = resp.get_result()
191
+		except AttributeError:
192
+			result = [res.get_output() for res in resp]
193
+
194
+		return result
... ...
@@ -1,5 +1,3 @@
1
-# $Id: server.py,v 1.8 2011/05/26 19:34:19 edwlan Exp $
2
-
3 1
 #
4 2
 #  Copyright (c) 2011 Edward Langley
5 3
 #  All rights reserved.
... ...
@@ -34,6 +32,7 @@
34 32
 #
35 33
 import jsonrpc.jsonutil
36 34
 from jsonrpc.utilities import public
35
+import jsonrpc.common
37 36
 
38 37
 # Twisted imports
39 38
 from twisted.web import server
... ...
@@ -56,7 +55,7 @@ class ServerEvents(object):
56 55
 	def callmethod(self, txrequest, rpcrequest, **extra):
57 56
 		'''Finds the method and calls it with the specified args'''
58 57
 		method = self.findmethod(rpcrequest.method)
59
-		if method is None: raise MethodNotFound
58
+		if method is None: raise jsonrpc.common.MethodNotFound
60 59
 		extra.update(rpcrequest.kwargs)
61 60
 
62 61
 		return method(*rpcrequest.args, **extra)
... ...
@@ -78,88 +77,6 @@ class ServerEvents(object):
78 77
 		return content
79 78
 
80 79
 
81
-@public
82
-class ServerError(Exception):
83
-	'''Base Exception for JSON-RPC Errors, if this or a subclass of this is raised by a JSON-RPC method,
84
-	The server will convert it into an appropriate error object
85
-	'''
86
-
87
-	#: Error code
88
-	code = 0
89
-	#: Error message
90
-	msg = ""
91
-
92
-	def json_equivalent(self):
93
-		'''return a dictionary which matches an JSON-RPC Response'''
94
-		return dict(code=self.code, message=self.msg)
95
-
96
-	def __str__(self):
97
-		return jsonrpc.jsonutil.encode(self)
98
-
99
-@public
100
-class InvalidRequest(ServerError):
101
-	'''Raise this when the Request object does not match the schema'''
102
-	code = -32600
103
-	msg = "Invalid Request."
104
-
105
-@public
106
-class MethodNotFound(ServerError):
107
-	'''Raise this when the desired method is not found'''
108
-	code = -32601
109
-	msg = "Procedure not found."
110
-
111
-@public
112
-class ParseError(ServerError):
113
-	'''Raise this when the request contains invalid JSON'''
114
-	code = -32700
115
-	msg = "Parse error."
116
-
117
-class Request(object):
118
-	def __init__(self, content):
119
-		self.version = content.pop('jsonrpc', None)
120
-		self.id = content.pop('id', None)
121
-
122
-		self.method = content.pop('method', None)
123
-
124
-		kwargs = content.pop('params', {})
125
-		args = ()
126
-		if not isinstance(kwargs, dict):
127
-			args = tuple(kwargs)
128
-			kwargs = {}
129
-		else:
130
-			args = kwargs.pop('__args', args)
131
-
132
-		self.args = args
133
-		self.kwargs = dict( (str(k), v) for k,v in kwargs.items() )
134
-		self.extra = content
135
-
136
-	def check(self):
137
-		if self.version != '2.0': raise InvalidRequest
138
-		if not isinstance(self.method, (str, unicode)): raise InvalidRequest
139
-
140
-	@classmethod
141
-	def from_list(cls, content):
142
-		result = []
143
-		for req in content:
144
-			result.append(cls(req))
145
-		return result
146
-
147
-
148
-class Response(object):
149
-	def __init__(self, id=None, result=None, error=None):
150
-		self.version = '2.0'
151
-		self.id = id
152
-		self.result = result
153
-		self.error = error
154
-
155
-	def json_equivalent(self):
156
-		res = dict(jsonrpc=self.version, id=self.id)
157
-		if self.error is None:
158
-			res['result'] = self.result
159
-		else:
160
-			res['error'] = self.error
161
-		return res
162
-
163 80
 
164 81
 ## Base class providing a JSON-RPC 2.0 implementation with 2 customizable hooks
165 82
 @public
... ...
@@ -186,14 +103,14 @@ class JSON_RPC(Resource):
186 103
 		try:
187 104
 			try:
188 105
 				content = jsonrpc.jsonutil.decode(request.content.read())
189
-			except ValueError: raise ParseError
106
+			except ValueError: raise jsonrpc.common.ParseError
190 107
 
191 108
 			content = self.eventhandler.processcontent(content, request)
192 109
 
193 110
 			if isinstance(content, list):
194
-				content = Request.from_list(content)
111
+				content = jsonrpc.common.Request.from_list(content)
195 112
 			else:
196
-				content = Request(content)
113
+				content = jsonrpc.common.Request.from_dict(content)
197 114
 
198 115
 			try:
199 116
 				if hasattr(content, 'check'):
... ...
@@ -201,7 +118,7 @@ class JSON_RPC(Resource):
201 118
 				else:
202 119
 					for item in content: item.check()
203 120
 
204
-			except ServerError, e:
121
+			except jsonrpc.common.RPCError, e:
205 122
 				self._ebRender(e, request, content.id if hasattr(content, 'id') else None)
206 123
 
207 124
 			d = threads.deferToThread(self._action, request, content)
... ...
@@ -218,7 +135,7 @@ class JSON_RPC(Resource):
218 135
 		islist = (True if isinstance(contents, list) else False)
219 136
 		if not islist: contents = [contents]
220 137
 
221
-		if contents == []: raise InvalidRequest
138
+		if contents == []: raise jsonrpc.common.InvalidRequest
222 139
 
223 140
 		for rpcrequest in contents:
224 141
 			res = None
... ...
@@ -226,7 +143,7 @@ class JSON_RPC(Resource):
226 143
 			try:
227 144
 				add = copy.deepcopy(rpcrequest.extra)
228 145
 				add.update(kw)
229
-				res = Response(id=rpcrequest.id, result=self.eventhandler.callmethod(request, rpcrequest, **add))
146
+				res = jsonrpc.common.Response(id=rpcrequest.id, result=self.eventhandler.callmethod(request, rpcrequest, **add))
230 147
 				res = self.eventhandler.processrequest(res, request.args, **kw)
231 148
 			except Exception, e:
232 149
 				res = self.render_error(e, rpcrequest.id)
... ...
@@ -274,10 +191,10 @@ class JSON_RPC(Resource):
274 191
 
275 192
 
276 193
 	def render_error(self, e, id):
277
-		if isinstance(e, ServerError):
278
-			err = Response(id=id, error=e)
194
+		if isinstance(e, jsonrpc.common.RPCError):
195
+			err = jsonrpc.common.Response(id=id, error=e)
279 196
 		else:
280
-			err = Response(id=id, error=dict(code=0, message=str(e), data=e.args))
197
+			err = jsonrpc.common.Response(id=id, error=dict(code=0, message=str(e), data=e.args))
281 198
 
282 199
 		return err
283 200