Tweaked _notify so it's a property instead of string method and tuple parameter passi...
[invirt/packages/python-jsonrpclib.git] / jsonrpclib.py
index 739cf70..34232d0 100644 (file)
@@ -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,9 @@ 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
@@ -147,24 +153,25 @@ 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)
-        print request
-        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
-        
+
         response = self.__transport.request(
             self.__host,
             self.__handler,
@@ -179,20 +186,20 @@ class ServerProxy(XMLServerProxy):
         # outputting the response appropriately?
         
         _last_response = response
-        if not response:
-            # notification, no result
-            return None
-        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 ' +
@@ -202,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
@@ -213,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:
@@ -227,46 +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 __run_request(self, request_body):
-        run_request = getattr(ServerProxy, '_ServerProxy__run_request')
-        return run_request(self, request_body)
+    def __iter__(self):
+        for i in range(0, len(self.results)):
+            yield self[i]
+        raise StopIteration
 
-    def __request(self):
+    def __getitem__(self, i):
+        item = self.results[i]
+        check_for_errors(item)
+        return item['result']
+
+    def __len__(self):
+        return len(self.results)
+
+class MultiCall(object):
+    
+    def __init__(self, server):
+        self.__server = server
+        self.__job_list = []
+
+    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)
+
+    @property
+    def _notify(self):
+        return MultiCallNotify()
 
-    def __notify(self, method, params=[]):
-        new_job = Job(method, notify=True)
-        new_job.params = params
-        self.__job_list.append(new_job)
-        
     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
@@ -279,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 '<Fault %s: %s>' % (self.faultCode, self.faultString)
 
 def random_id(length=8):
     import string
@@ -343,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 \
@@ -354,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:
@@ -366,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.')
@@ -394,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):