X-Git-Url: http://xvm.mit.edu/gitweb/invirt/packages/python-jsonrpclib.git/blobdiff_plain/d3944dc087d6637c9de2b153a45a0c83d5f3ebbe..25cdc01f9427dcc3383d32c53164baf052d1131e:/jsonrpclib.py diff --git a/jsonrpclib.py b/jsonrpclib.py index 7be24b4..34232d0 100644 --- a/jsonrpclib.py +++ b/jsonrpclib.py @@ -21,7 +21,13 @@ appropriately. >>> server = jsonrpclib.Server('http://localhost:8181') >>> server.add(5, 6) 11 ->>> jsonrpclib.__notify('add', (5, 6)) +>>> server._notify.add(5, 6) +>>> batch = jsonrpclib.MultiCall(server) +>>> batch.add(3, 50) +>>> batch.add(2, 3) +>>> batch._notify.add(3, 5) +>>> batch() +[53, 5] See http://code.google.com/p/jsonrpclib/ for more info. """ @@ -103,9 +109,12 @@ class Transport(XMLTransport): response = file_h.read(1024) if not response: break + response_body += response if self.verbose: print 'body: %s' % response - response_body += response + if response_body == '': + # Notification + return None return_obj = loads(response_body) return return_obj @@ -144,26 +153,24 @@ class ServerProxy(XMLServerProxy): self.__encoding = encoding self.__verbose = verbose - def __request(self, methodname, params, rpcid=None): + def _request(self, methodname, params, rpcid=None): request = dumps(params, methodname, encoding=self.__encoding, rpcid=rpcid, version=self.__version) - response = self.__run_request(request) + response = self._run_request(request) + check_for_errors(response) return response['result'] - - def __notify(self, methodname, params, rpcid=None): + + def _request_notify(self, methodname, params, rpcid=None): request = dumps(params, methodname, encoding=self.__encoding, rpcid=rpcid, version=self.__version, notify=True) - response = self.__run_request(request, notify=True) + response = self._run_request(request, notify=True) + check_for_errors(response) return - def __run_request(self, request, notify=None): + def _run_request(self, request, notify=None): global _last_request global _last_response _last_request = request - - if notify is True: - _last_response = None - return None response = self.__transport.request( self.__host, @@ -179,17 +186,20 @@ class ServerProxy(XMLServerProxy): # outputting the response appropriately? _last_response = response - return check_for_errors(response) + return response def __getattr__(self, name): - # Same as original, just with new _Method and wrapper - # for __notify - if name in ('__notify', '__run_request'): - wrapped_name = '_%s%s' % (self.__class__.__name__, name) - return getattr(self, wrapped_name) - return _Method(self.__request, name) + # Same as original, just with new _Method reference + return _Method(self._request, name) + + @property + def _notify(self): + # Just like __getattr__, but with notify namespace. + return _Notify(self._request_notify) + class _Method(XML_Method): + def __call__(self, *args, **kwargs): if len(args) > 0 and len(kwargs) > 0: raise ProtocolError('Cannot use both positional ' + @@ -199,9 +209,16 @@ class _Method(XML_Method): else: return self.__send(self.__name, kwargs) +class _Notify(object): + def __init__(self, request): + self._request = request + + def __getattr__(self, name): + return _Method(self._request, name) + # Batch implementation -class Job(object): +class MultiCallMethod(object): def __init__(self, method, notify=False): self.method = method @@ -210,8 +227,8 @@ class Job(object): def __call__(self, *args, **kwargs): if len(kwargs) > 0 and len(args) > 0: - raise ProtocolError('A Job cannot have both positional ' + - 'and keyword arguments.') + raise ProtocolError('JSON-RPC does not support both ' + + 'positional and keyword arguments.') if len(kwargs) > 0: self.params = kwargs else: @@ -224,45 +241,60 @@ class Job(object): def __repr__(self): return '%s' % self.request() -class MultiCall(ServerProxy): +class MultiCallNotify(object): + def __getattr__(self, name): + return MultiCallMethod(name, notify=True) + +class MultiCallIterator(object): - def __init__(self, uri, *args, **kwargs): - self.__job_list = [] - ServerProxy.__init__(self, uri, *args, **kwargs) + def __init__(self, results): + self.results = results + + def __iter__(self): + for i in range(0, len(self.results)): + yield self[i] + raise StopIteration + + def __getitem__(self, i): + item = self.results[i] + check_for_errors(item) + return item['result'] + + def __len__(self): + return len(self.results) - def __run_request(self, request_body): - run_request = getattr(ServerProxy, '_ServerProxy__run_request') - return run_request(self, request_body) +class MultiCall(object): + + def __init__(self, server): + self.__server = server + self.__job_list = [] - def __request(self): + def _request(self): if len(self.__job_list) < 1: # Should we alert? This /is/ pretty obvious. return request_body = '[ %s ]' % ','.join([job.request() for job in self.__job_list]) - responses = self.__run_request(request_body) + responses = self.__server._run_request(request_body) del self.__job_list[:] - return [ response['result'] for response in responses ] + return MultiCallIterator(responses) - def __notify(self, method, params): - new_job = Job(method, notify=True) - self.__job_list.append(new_job) + @property + def _notify(self): + return MultiCallNotify() def __getattr__(self, name): - if name in ('__run', '__notify'): - wrapped_name = '_%s%s' % (self.__class__.__name__, name) - return getattr(self, wrapped_name) - new_job = Job(name) + new_job = MultiCallMethod(name) self.__job_list.append(new_job) return new_job - __call__ = __request + __call__ = _request # These lines conform to xmlrpclib's "compatibility" line. # Not really sure if we should include these, but oh well. Server = ServerProxy -class Fault(dict): +class Fault(object): # JSON-RPC error class def __init__(self, code=-32000, message='Server error'): self.faultCode = code @@ -275,8 +307,10 @@ class Fault(dict): global _version if not version: version = _version - return dumps(self, rpcid=None, methodresponse=True, - version=version) + return dumps(self, rpcid=rpcid, version=version) + + def __repr__(self): + return '' % (self.faultCode, self.faultString) def random_id(length=8): import string @@ -339,7 +373,7 @@ def dumps(params=[], methodname=None, methodresponse=None, """ global _version if not version: - verion = _version + version = _version valid_params = (types.TupleType, types.ListType, types.DictType) if methodname in types.StringTypes and \ type(params) not in valid_params and \ @@ -350,11 +384,6 @@ def dumps(params=[], methodname=None, methodresponse=None, """ raise TypeError('Params must be a dict, list, tuple or Fault ' + 'instance.') - if type(methodname) not in types.StringTypes and methodresponse != True: - raise ValueError('Method name must be a string, or methodresponse '+ - 'must be set to True.') - if isinstance(params, Fault) and not methodresponse: - raise TypeError('You can only use a Fault for responses.') # Begin parsing object payload = Payload(rpcid=rpcid, version=version) if not encoding: @@ -362,6 +391,9 @@ def dumps(params=[], methodname=None, methodresponse=None, if type(params) is Fault: response = payload.error(params.faultCode, params.faultString) return jdumps(response, encoding=encoding) + if type(methodname) not in types.StringTypes and methodresponse != True: + raise ValueError('Method name must be a string, or methodresponse '+ + 'must be set to True.') if methodresponse is True: if rpcid is None: raise ValueError('A method response must have an rpcid.') @@ -380,6 +412,9 @@ def loads(data): the request structure in Dict format instead of the method, params. It will return a list in the case of a batch request / response. """ + if data == '': + # notification + return None result = jloads(data) # if the above raises an error, the implementing server code # should return something like the following: @@ -387,19 +422,19 @@ def loads(data): return result def check_for_errors(result): - result_list = [] - if not isbatch(result): - result_list.append(result) - else: - result_list = result - for entry in result_list: - if 'jsonrpc' in entry.keys() and float(entry['jsonrpc']) > 2.0: - raise NotImplementedError('JSON-RPC version not yet supported.') - if 'error' in entry.keys() and entry['error'] != None: - code = entry['error']['code'] - message = entry['error']['message'] - raise ProtocolError('ERROR %s: %s' % (code, message)) - del result_list + if not result: + # Notification + return result + if type(result) is not types.DictType: + raise TypeError('Response is not a dict.') + if 'jsonrpc' in result.keys() and float(result['jsonrpc']) > 2.0: + raise NotImplementedError('JSON-RPC version not yet supported.') + if 'result' not in result.keys() and 'error' not in result.keys(): + raise ValueError('Response does not have a result or error key.') + if 'error' in result.keys() and result['error'] != None: + code = result['error']['code'] + message = result['error']['message'] + raise ProtocolError('ERROR %s: %s' % (code, message)) return result def isbatch(result):