jsonrpclib-0.1.7/0000775000175000017500000000000012631227001014522 5ustar travistravis00000000000000jsonrpclib-0.1.7/jsonrpclib/0000775000175000017500000000000012631227001016667 5ustar travistravis00000000000000jsonrpclib-0.1.7/jsonrpclib/SimpleJSONRPCServer.py0000664000175000017500000002061512631226744022741 0ustar travistravis00000000000000import jsonrpclib from jsonrpclib import Fault from jsonrpclib.jsonrpc import USE_UNIX_SOCKETS import SimpleXMLRPCServer import SocketServer import socket import logging import os import types import traceback import sys try: import fcntl except ImportError: # For Windows fcntl = None def get_version(request): # must be a dict if 'jsonrpc' in request.keys(): return 2.0 if 'id' in request.keys(): return 1.0 return None def validate_request(request): if not isinstance(request, dict): fault = Fault( -32600, 'Request must be {}, not %s.' % type(request) ) return fault rpcid = request.get('id', None) version = get_version(request) if not version: fault = Fault(-32600, 'Request %s invalid.' % request, rpcid=rpcid) return fault request.setdefault('params', []) method = request.get('method', None) params = request.get('params') param_types = (types.ListType, types.DictType, types.TupleType) if not method or type(method) not in types.StringTypes or \ type(params) not in param_types: fault = Fault( -32600, 'Invalid request parameters or method.', rpcid=rpcid ) return fault return True class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher): def __init__(self, encoding=None): SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__( self, allow_none=True, encoding=encoding) def _marshaled_dispatch(self, data, dispatch_method=None): response = None try: request = jsonrpclib.loads(data) except Exception, e: fault = Fault(-32700, 'Request %s invalid. (%s)' % (data, e)) response = fault.response() return response if not request: fault = Fault(-32600, 'Request invalid -- no request data.') return fault.response() if isinstance(request, list): # This SHOULD be a batch, by spec responses = [] for req_entry in request: result = validate_request(req_entry) if type(result) is Fault: responses.append(result.response()) continue resp_entry = self._marshaled_single_dispatch(req_entry) if resp_entry is not None: responses.append(resp_entry) if len(responses) > 0: response = '[%s]' % ','.join(responses) else: response = '' else: result = validate_request(request) if type(result) is Fault: return result.response() response = self._marshaled_single_dispatch(request) return response def _marshaled_single_dispatch(self, request): # TODO - Use the multiprocessing and skip the response if # it is a notification # Put in support for custom dispatcher here # (See SimpleXMLRPCServer._marshaled_dispatch) method = request.get('method') params = request.get('params') try: response = self._dispatch(method, params) except: exc_type, exc_value, exc_tb = sys.exc_info() fault = Fault(-32603, '%s:%s' % (exc_type, exc_value)) return fault.response() if 'id' not in request.keys() or request['id'] is None: # It's a notification return None try: response = jsonrpclib.dumps(response, methodresponse=True, rpcid=request['id'] ) return response except: exc_type, exc_value, exc_tb = sys.exc_info() fault = Fault(-32603, '%s:%s' % (exc_type, exc_value)) return fault.response() def _dispatch(self, method, params): func = None try: func = self.funcs[method] except KeyError: if self.instance is not None: if hasattr(self.instance, '_dispatch'): return self.instance._dispatch(method, params) else: try: func = SimpleXMLRPCServer.resolve_dotted_attribute( self.instance, method, True ) except AttributeError: pass if func is not None: try: if isinstance(params, types.ListType): response = func(*params) else: response = func(**params) return response # except TypeError: # return Fault(-32602, 'Invalid parameters.') except: err_lines = traceback.format_exc().splitlines() trace_string = '%s | %s' % (err_lines[-3], err_lines[-1]) fault = jsonrpclib.Fault(-32603, 'Server error: %s' % trace_string) return fault else: return Fault(-32601, 'Method %s not supported.' % method) class SimpleJSONRPCRequestHandler( SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): def do_POST(self): if not self.is_rpc_path_valid(): self.report_404() return try: max_chunk_size = 10*1024*1024 size_remaining = int(self.headers["content-length"]) L = [] while size_remaining: chunk_size = min(size_remaining, max_chunk_size) L.append(self.rfile.read(chunk_size)) size_remaining -= len(L[-1]) data = ''.join(L) response = self.server._marshaled_dispatch(data) self.send_response(200) except Exception: self.send_response(500) err_lines = traceback.format_exc().splitlines() trace_string = '%s | %s' % (err_lines[-3], err_lines[-1]) fault = jsonrpclib.Fault(-32603, 'Server error: %s' % trace_string) response = fault.response() if response is None: response = '' self.send_header("Content-type", "application/json-rpc") self.send_header("Content-length", str(len(response))) self.end_headers() self.wfile.write(response) self.wfile.flush() self.connection.shutdown(1) class SimpleJSONRPCServer(SocketServer.TCPServer, SimpleJSONRPCDispatcher): allow_reuse_address = True def __init__(self, addr, requestHandler=SimpleJSONRPCRequestHandler, logRequests=True, encoding=None, bind_and_activate=True, address_family=socket.AF_INET): self.logRequests = logRequests SimpleJSONRPCDispatcher.__init__(self, encoding) # TCPServer.__init__ has an extra parameter on 2.6+, so # check Python version and decide on how to call it vi = sys.version_info self.address_family = address_family if USE_UNIX_SOCKETS and address_family == socket.AF_UNIX: # Unix sockets can't be bound if they already exist in the # filesystem. The convention of e.g. X11 is to unlink # before binding again. if os.path.exists(addr): try: os.unlink(addr) except OSError: logging.warning("Could not unlink socket %s", addr) # if python 2.5 and lower if vi[0] < 3 and vi[1] < 6: SocketServer.TCPServer.__init__(self, addr, requestHandler) else: SocketServer.TCPServer.__init__( self, addr, requestHandler, bind_and_activate) if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'): flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD) flags |= fcntl.FD_CLOEXEC fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags) class CGIJSONRPCRequestHandler(SimpleJSONRPCDispatcher): def __init__(self, encoding=None): SimpleJSONRPCDispatcher.__init__(self, encoding) def handle_jsonrpc(self, request_text): response = self._marshaled_dispatch(request_text) print 'Content-Type: application/json-rpc' print 'Content-Length: %d' % len(response) print sys.stdout.write(response) handle_xmlrpc = handle_jsonrpc jsonrpclib-0.1.7/jsonrpclib/__init__.py0000664000175000017500000000036712631226744021023 0ustar travistravis00000000000000from jsonrpclib.config import Config config = Config.instance() from jsonrpclib.history import History history = History.instance() from jsonrpclib.jsonrpc import Server, MultiCall, Fault from jsonrpclib.jsonrpc import ProtocolError, loads, dumps jsonrpclib-0.1.7/jsonrpclib/config.py0000664000175000017500000000260612631226744020527 0ustar travistravis00000000000000import sys class LocalClasses(dict): def add(self, cls): self[cls.__name__] = cls class Config(object): """ This is pretty much used exclusively for the 'jsonclass' functionality... set use_jsonclass to False to turn it off. You can change serialize_method and ignore_attribute, or use the local_classes.add(class) to include "local" classes. """ use_jsonclass = True # Change to False to keep __jsonclass__ entries raw. serialize_method = '_serialize' # The serialize_method should be a string that references the # method on a custom class object which is responsible for # returning a tuple of the constructor arguments and a dict of # attributes. ignore_attribute = '_ignore' # The ignore attribute should be a string that references the # attribute on a custom class object which holds strings and / or # references of the attributes the class translator should ignore. classes = LocalClasses() # The list of classes to use for jsonclass translation. version = 2.0 # Version of the JSON-RPC spec to support user_agent = 'jsonrpclib/0.1 (Python %s)' % \ '.'.join([str(ver) for ver in sys.version_info[0:3]]) # User agent to use for calls. _instance = None @classmethod def instance(cls): if not cls._instance: cls._instance = cls() return cls._instance jsonrpclib-0.1.7/jsonrpclib/history.py0000664000175000017500000000171112631226744020757 0ustar travistravis00000000000000class History(object): """ This holds all the response and request objects for a session. A server using this should call "clear" after each request cycle in order to keep it from clogging memory. """ requests = [] responses = [] _instance = None @classmethod def instance(cls): if not cls._instance: cls._instance = cls() return cls._instance def add_response(self, response_obj): self.responses.append(response_obj) def add_request(self, request_obj): self.requests.append(request_obj) @property def request(self): if len(self.requests) == 0: return None else: return self.requests[-1] @property def response(self): if len(self.responses) == 0: return None else: return self.responses[-1] def clear(self): del self.requests[:] del self.responses[:] jsonrpclib-0.1.7/jsonrpclib/jsonclass.py0000664000175000017500000001232112631226744021254 0ustar travistravis00000000000000import types import inspect import re from jsonrpclib import config iter_types = [ types.DictType, types.ListType, types.TupleType ] string_types = [ types.StringType, types.UnicodeType ] numeric_types = [ types.IntType, types.LongType, types.FloatType ] value_types = [ types.BooleanType, types.NoneType ] supported_types = iter_types+string_types+numeric_types+value_types invalid_module_chars = r'[^a-zA-Z0-9\_\.]' class TranslationError(Exception): pass def dump(obj, serialize_method=None, ignore_attribute=None, ignore=[]): if not serialize_method: serialize_method = config.serialize_method if not ignore_attribute: ignore_attribute = config.ignore_attribute obj_type = type(obj) # Parse / return default "types"... if obj_type in numeric_types+string_types+value_types: return obj if obj_type in iter_types: if obj_type in (types.ListType, types.TupleType): new_obj = [] for item in obj: new_obj.append( dump(item, serialize_method, ignore_attribute, ignore)) if isinstance(obj_type, types.TupleType): new_obj = tuple(new_obj) return new_obj # It's a dict... else: new_obj = {} for key, value in obj.iteritems(): new_obj[key] = dump( value, serialize_method, ignore_attribute, ignore) return new_obj # It's not a standard type, so it needs __jsonclass__ module_name = inspect.getmodule(obj).__name__ class_name = obj.__class__.__name__ json_class = class_name if module_name not in ['', '__main__']: json_class = '%s.%s' % (module_name, json_class) return_obj = {"__jsonclass__": [json_class]} # If a serialization method is defined.. if serialize_method in dir(obj): # Params can be a dict (keyword) or list (positional) # Attrs MUST be a dict. serialize = getattr(obj, serialize_method) params, attrs = serialize() return_obj['__jsonclass__'].append(params) return_obj.update(attrs) return return_obj # Otherwise, try to figure it out # Obviously, we can't assume to know anything about the # parameters passed to __init__ return_obj['__jsonclass__'].append([]) attrs = {} ignore_list = getattr(obj, ignore_attribute, [])+ignore for attr_name, attr_value in obj.__dict__.iteritems(): if type(attr_value) in supported_types and \ attr_name not in ignore_list and \ attr_value not in ignore_list: attrs[attr_name] = dump( attr_value, serialize_method, ignore_attribute, ignore) return_obj.update(attrs) return return_obj def load(obj): if type(obj) in string_types + numeric_types + value_types: return obj if isinstance(obj, list): return_list = [] for entry in obj: return_list.append(load(entry)) return return_list # Othewise, it's a dict type if '__jsonclass__' not in obj: return_dict = {} for key, value in obj.iteritems(): new_value = load(value) return_dict[key] = new_value return return_dict # It's a dict, and it's a __jsonclass__ orig_module_name = obj['__jsonclass__'][0] params = obj['__jsonclass__'][1] if orig_module_name == '': raise TranslationError('Module name empty.') json_module_clean = re.sub(invalid_module_chars, '', orig_module_name) if json_module_clean != orig_module_name: raise TranslationError('Module name %s has invalid characters.' % orig_module_name) json_module_parts = json_module_clean.split('.') json_class = None if len(json_module_parts) == 1: # Local class name -- probably means it won't work if json_module_parts[0] not in config.classes.keys(): raise TranslationError('Unknown class or module %s.' % json_module_parts[0]) json_class = config.classes[json_module_parts[0]] else: json_class_name = json_module_parts.pop() json_module_tree = '.'.join(json_module_parts) try: temp_module = __import__(json_module_tree) except ImportError: raise TranslationError('Could not import %s from module %s.' % (json_class_name, json_module_tree)) # The returned class is the top-level module, not the one we really # want. (E.g., if we import a.b.c, we now have a.) Walk through other # path components to get to b and c. for i in json_module_parts[1:]: temp_module = getattr(temp_module, i) json_class = getattr(temp_module, json_class_name) # Creating the object... new_obj = None if isinstance(params, list): new_obj = json_class(*params) elif isinstance(params, dict): new_obj = json_class(**params) else: raise TranslationError('Constructor args must be a dict or list.') for key, value in obj.iteritems(): if key == '__jsonclass__': continue setattr(new_obj, key, value) return new_obj jsonrpclib-0.1.7/jsonrpclib/jsonrpc.py0000664000175000017500000004152312631226744020741 0ustar travistravis00000000000000""" Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ============================ JSONRPC Library (jsonrpclib) ============================ This library is a JSON-RPC v.2 (proposed) implementation which follows the xmlrpclib API for portability between clients. It uses the same Server / ServerProxy, loads, dumps, etc. syntax, while providing features not present in XML-RPC like: * Keyword arguments * Notifications * Versioning * Batches and batch notifications Eventually, I'll add a SimpleXMLRPCServer compatible library, and other things to tie the thing off nicely. :) For a quick-start, just open a console and type the following, replacing the server address, method, and parameters appropriately. >>> import jsonrpclib >>> server = jsonrpclib.Server('http://localhost:8181') >>> server.add(5, 6) 11 >>> 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. """ import types from xmlrpclib import Transport as XMLTransport from xmlrpclib import SafeTransport as XMLSafeTransport from xmlrpclib import ServerProxy as XMLServerProxy from xmlrpclib import _Method as XML_Method import string import random # Library includes from jsonrpclib import config from jsonrpclib import history # JSON library importing cjson = None json = None try: import cjson except ImportError: try: import json except ImportError: try: import simplejson as json except ImportError: raise ImportError( 'You must have the cjson, json, or simplejson ' + 'module(s) available.' ) IDCHARS = string.ascii_lowercase+string.digits class UnixSocketMissing(Exception): """ Just a properly named Exception if Unix Sockets usage is attempted on a platform that doesn't support them (Windows) """ pass # JSON Abstractions def jdumps(obj, encoding='utf-8'): # Do 'serialize' test at some point for other classes global cjson if cjson: return cjson.encode(obj) else: return json.dumps(obj, encoding=encoding) def jloads(json_string): global cjson if cjson: return cjson.decode(json_string) else: return json.loads(json_string) # XMLRPClib re-implementations class ProtocolError(Exception): pass class TransportMixIn(object): """ Just extends the XMLRPC transport where necessary. """ user_agent = config.user_agent # for Python 2.7 support _connection = (None, None) _extra_headers = [] def send_content(self, connection, request_body): connection.putheader("Content-Type", "application/json-rpc") connection.putheader("Content-Length", str(len(request_body))) connection.endheaders() if request_body: connection.send(request_body) def getparser(self): target = JSONTarget() return JSONParser(target), target class JSONParser(object): def __init__(self, target): self.target = target def feed(self, data): self.target.feed(data) def close(self): pass class JSONTarget(object): def __init__(self): self.data = [] def feed(self, data): self.data.append(data) def close(self): return ''.join(self.data) class Transport(TransportMixIn, XMLTransport): def __init__(self): TransportMixIn.__init__(self) XMLTransport.__init__(self) class SafeTransport(TransportMixIn, XMLSafeTransport): def __init__(self): TransportMixIn.__init__(self) XMLSafeTransport.__init__(self) from httplib import HTTP, HTTPConnection from socket import socket USE_UNIX_SOCKETS = False try: from socket import AF_UNIX, SOCK_STREAM USE_UNIX_SOCKETS = True except ImportError: pass if (USE_UNIX_SOCKETS): class UnixHTTPConnection(HTTPConnection): def connect(self): self.sock = socket(AF_UNIX, SOCK_STREAM) self.sock.connect(self.host) class UnixHTTP(HTTP): _connection_class = UnixHTTPConnection class UnixTransport(TransportMixIn, XMLTransport): def make_connection(self, host): host, extra_headers, x509 = self.get_host_info(host) return UnixHTTP(host) class ServerProxy(XMLServerProxy): """ Unfortunately, much more of this class has to be copied since so much of it does the serialization. """ def __init__(self, uri, transport=None, encoding=None, verbose=0, version=None): import urllib if not version: version = config.version self.__version = version schema, uri = urllib.splittype(uri) if schema not in ('http', 'https', 'unix'): raise IOError('Unsupported JSON-RPC protocol.') if schema == 'unix': if not USE_UNIX_SOCKETS: # Don't like the "generic" Exception... raise UnixSocketMissing("Unix sockets not available.") self.__host = uri self.__handler = '/' else: self.__host, self.__handler = urllib.splithost(uri) if not self.__handler: # Not sure if this is in the JSON spec? # self.__handler = '/' self.__handler == '/' if transport is None: if schema == 'unix': transport = UnixTransport() elif schema == 'https': transport = SafeTransport() else: transport = Transport() self.__transport = transport self.__encoding = encoding self.__verbose = verbose def _request(self, methodname, params, rpcid=None): request = dumps(params, methodname, encoding=self.__encoding, rpcid=rpcid, version=self.__version) response = self._run_request(request) check_for_errors(response) return response['result'] 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) check_for_errors(response) return def _run_request(self, request, notify=None): history.add_request(request) response = self.__transport.request( self.__host, self.__handler, request, verbose=self.__verbose ) # Here, the XMLRPC library translates a single list # response to the single value -- should we do the # same, and require a tuple / list to be passed to # the response object, or expect the Server to be # outputting the response appropriately? history.add_response(response) if not response: return None return_obj = loads(response) return return_obj def __getattr__(self, 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 and keyword arguments ' '(according to JSON-RPC spec.)') if len(args) > 0: return self.__send(self.__name, args) else: return self.__send(self.__name, kwargs) def __getattr__(self, name): return _Method(self.__send, "%s.%s" % (self.__name, name)) def __repr__(self): return '<{} "{}">'.format(self.__class__.__name__, self.__name) def __str__(self): return self.__repr__() def __dir__(self): return self.__dict__.keys() class _Notify(object): def __init__(self, request): self._request = request def __getattr__(self, name): return _Method(self._request, name) # Batch implementation class MultiCallMethod(object): def __init__(self, method, notify=False): self.method = method self.params = [] self.notify = notify def __call__(self, *args, **kwargs): if len(kwargs) > 0 and len(args) > 0: raise ProtocolError('JSON-RPC does not support both ' + 'positional and keyword arguments.') if len(kwargs) > 0: self.params = kwargs else: self.params = args def request(self, encoding=None, rpcid=None): return dumps(self.params, self.method, version=2.0, encoding=encoding, rpcid=rpcid, notify=self.notify) def __repr__(self): return '%s' % self.request() def __getattr__(self, method): new_method = '%s.%s' % (self.method, method) self.method = new_method return self class MultiCallNotify(object): def __init__(self, multicall): self.multicall = multicall def __getattr__(self, name): new_job = MultiCallMethod(name, notify=True) self.multicall._job_list.append(new_job) return new_job class MultiCallIterator(object): 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) 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 = '[ {0} ]'.format( ','.join([job.request() for job in self._job_list])) responses = self._server._run_request(request_body) del self._job_list[:] if not responses: responses = [] return MultiCallIterator(responses) @property def _notify(self): return MultiCallNotify(self) def __getattr__(self, name): new_job = MultiCallMethod(name) self._job_list.append(new_job) return new_job __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(object): # JSON-RPC error class def __init__(self, code=-32000, message='Server error', rpcid=None): self.faultCode = code self.faultString = message self.rpcid = rpcid def error(self): return {'code': self.faultCode, 'message': self.faultString} def response(self, rpcid=None, version=None): if not version: version = config.version if rpcid: self.rpcid = rpcid return dumps( self, methodresponse=True, rpcid=self.rpcid, version=version ) def __repr__(self): return '' % (self.faultCode, self.faultString) def random_id(length=8): return_id = '' for i in range(length): return_id += random.choice(IDCHARS) return return_id class Payload(dict): def __init__(self, rpcid=None, version=None): if not version: version = config.version self.id = rpcid self.version = float(version) def request(self, method, params=[]): if type(method) not in types.StringTypes: raise ValueError('Method name must be a string.') if not self.id: self.id = random_id() request = {'id': self.id, 'method': method} if params: request['params'] = params if self.version >= 2: request['jsonrpc'] = str(self.version) return request def notify(self, method, params=[]): request = self.request(method, params) if self.version >= 2: del request['id'] else: request['id'] = None return request def response(self, result=None): response = {'result': result, 'id': self.id} if self.version >= 2: response['jsonrpc'] = str(self.version) else: response['error'] = None return response def error(self, code=-32000, message='Server error.'): error = self.response() if self.version >= 2: del error['result'] else: error['result'] = None error['error'] = {'code': code, 'message': message} return error def dumps( params=[], methodname=None, methodresponse=None, encoding=None, rpcid=None, version=None, notify=None): """ This differs from the Python implementation in that it implements the rpcid argument since the 2.0 spec requires it for responses. """ if not version: version = config.version valid_params = (types.TupleType, types.ListType, types.DictType) if methodname in types.StringTypes and \ type(params) not in valid_params and \ not isinstance(params, Fault): """ If a method, and params are not in a listish or a Fault, error out. """ raise TypeError('Params must be a dict, list, tuple or Fault ' + 'instance.') # Begin parsing object payload = Payload(rpcid=rpcid, version=version) if not encoding: encoding = 'utf-8' 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 is not True: raise ValueError( 'Method name must be a string, or methodresponse must ' 'be set to True.') if config.use_jsonclass is True: from jsonrpclib import jsonclass params = jsonclass.dump(params) if methodresponse is True: if rpcid is None: raise ValueError('A method response must have an rpcid.') response = payload.response(params) return jdumps(response, encoding=encoding) request = None if notify is True: request = payload.notify(methodname, params) else: request = payload.request(methodname, params) return jdumps(request, encoding=encoding) def loads(data): """ This differs from the Python implementation, in that it returns 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: # { 'jsonrpc':'2.0', 'error': fault.error(), id: None } if config.use_jsonclass is True: from jsonrpclib import jsonclass result = jsonclass.load(result) return result def check_for_errors(result): if not result: # Notification return result if not isinstance(result, dict): 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'] is not None: code = result['error']['code'] message = result['error']['message'] raise ProtocolError((code, message)) return result def isbatch(result): if type(result) not in (types.ListType, types.TupleType): return False if len(result) < 1: return False if not isinstance(result[0], dict): return False if 'jsonrpc' not in result[0].keys(): return False try: version = float(result[0]['jsonrpc']) except ValueError: raise ProtocolError('"jsonrpc" key must be a float(able) value.') if version < 2: return False return True def isnotification(request): if 'id' not in request.keys(): # 2.0 notification return True if request['id'] is None: # 1.0 notification return True return False jsonrpclib-0.1.7/LICENSE.txt0000664000175000017500000000101412631226744016356 0ustar travistravis00000000000000Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. jsonrpclib-0.1.7/README.md0000664000175000017500000002033212631226744016016 0ustar travistravis00000000000000[![Build Status](https://travis-ci.org/joshmarshall/jsonrpclib.svg)](https://travis-ci.org/joshmarshall/jsonrpclib) JSONRPClib ========== This library is an implementation of the JSON-RPC specification. It supports both the original 1.0 specification, as well as the new (proposed) 2.0 spec, which includes batch submission, keyword arguments, etc. It is licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html). Communication ------------- Feel free to send any questions, comments, or patches to our Google Group mailing list (you'll need to join to send a message): http://groups.google.com/group/jsonrpclib Summary ------- This library implements the JSON-RPC 2.0 proposed specification in pure Python. It is designed to be as compatible with the syntax of xmlrpclib as possible (it extends where possible), so that projects using xmlrpclib could easily be modified to use JSON and experiment with the differences. It is backwards-compatible with the 1.0 specification, and supports all of the new proposed features of 2.0, including: * Batch submission (via MultiCall) * Keyword arguments * Notifications (both in a batch and 'normal') * Class translation using the 'jsonclass' key. I've added a "SimpleJSONRPCServer", which is intended to emulate the "SimpleXMLRPCServer" from the default Python distribution. Requirements ------------ It supports cjson and simplejson, and looks for the parsers in that order (searching first for cjson, then for the "built-in" simplejson as json in 2.6+, and then the simplejson external library). One of these must be installed to use this library, although if you have a standard distribution of 2.6+, you should already have one. Keep in mind that cjson is supposed to be the quickest, I believe, so if you are going for full-on optimization you may want to pick it up. Installation ------------ You can install this from PyPI with one of the following commands (sudo may be required): easy_install jsonrpclib pip install jsonrpclib Alternatively, you can download the source from the github repository at http://github.com/joshmarshall/jsonrpclib and manually install it with the following commands: git clone git://github.com/joshmarshall/jsonrpclib.git cd jsonrpclib python setup.py install Client Usage ------------ This is (obviously) taken from a console session. >>> import jsonrpclib >>> server = jsonrpclib.Server('http://localhost:8080') >>> server.add(5,6) 11 >>> print jsonrpclib.history.request {"jsonrpc": "2.0", "params": [5, 6], "id": "gb3c9g37", "method": "add"} >>> print jsonrpclib.history.response {'jsonrpc': '2.0', 'result': 11, 'id': 'gb3c9g37'} >>> server.add(x=5, y=10) 15 >>> server._notify.add(5,6) # No result returned... >>> batch = jsonrpclib.MultiCall(server) >>> batch.add(5, 6) >>> batch.ping({'key':'value'}) >>> batch._notify.add(4, 30) >>> results = batch() >>> for result in results: >>> ... print result 11 {'key': 'value'} # Note that there are only two responses -- this is according to spec. If you need 1.0 functionality, there are a bunch of places you can pass that in, although the best is just to change the value on jsonrpclib.config.version: >>> import jsonrpclib >>> jsonrpclib.config.version 2.0 >>> jsonrpclib.config.version = 1.0 >>> server = jsonrpclib.Server('http://localhost:8080') >>> server.add(7, 10) 17 >>> print jsonrpclib..history.request {"params": [7, 10], "id": "thes7tl2", "method": "add"} >>> print jsonrpclib.history.response {'id': 'thes7tl2', 'result': 17, 'error': None} >>> The equivalent loads and dumps functions also exist, although with minor modifications. The dumps arguments are almost identical, but it adds three arguments: rpcid for the 'id' key, version to specify the JSON-RPC compatibility, and notify if it's a request that you want to be a notification. Additionally, the loads method does not return the params and method like xmlrpclib, but instead a.) parses for errors, raising ProtocolErrors, and b.) returns the entire structure of the request / response for manual parsing. SimpleJSONRPCServer ------------------- This is identical in usage (or should be) to the SimpleXMLRPCServer in the default Python install. Some of the differences in features are that it obviously supports notification, batch calls, class translation (if left on), etc. Note: The import line is slightly different from the regular SimpleXMLRPCServer, since the SimpleJSONRPCServer is distributed within the jsonrpclib library. from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer server = SimpleJSONRPCServer(('localhost', 8080)) server.register_function(pow) server.register_function(lambda x,y: x+y, 'add') server.register_function(lambda x: x, 'ping') server.serve_forever() Class Translation ----------------- I've recently added "automatic" class translation support, although it is turned off by default. This can be devastatingly slow if improperly used, so the following is just a short list of things to keep in mind when using it. * Keep It (the object) Simple Stupid. (for exceptions, keep reading.) * Do not require init params (for exceptions, keep reading) * Getter properties without setters could be dangerous (read: not tested) If any of the above are issues, use the _serialize method. (see usage below) The server and client must BOTH have use_jsonclass configuration item on and they must both have access to the same libraries used by the objects for this to work. If you have excessively nested arguments, it would be better to turn off the translation and manually invoke it on specific objects using jsonrpclib.jsonclass.dump / jsonrpclib.jsonclass.load (since the default behavior recursively goes through attributes and lists / dicts / tuples). [test_obj.py] # This object is /very/ simple, and the system will look through the # attributes and serialize what it can. class TestObj(object): foo = 'bar' # This object requires __init__ params, so it uses the _serialize method # and returns a tuple of init params and attribute values (the init params # can be a dict or a list, but the attribute values must be a dict.) class TestSerial(object): foo = 'bar' def __init__(self, *args): self.args = args def _serialize(self): return (self.args, {'foo':self.foo,}) [usage] import jsonrpclib import test_obj jsonrpclib.config.use_jsonclass = True testobj1 = test_obj.TestObj() testobj2 = test_obj.TestSerial() server = jsonrpclib.Server('http://localhost:8080') # The 'ping' just returns whatever is sent ping1 = server.ping(testobj1) ping2 = server.ping(testobj2) print jsonrpclib.history.request # {"jsonrpc": "2.0", "params": [{"__jsonclass__": ["test_obj.TestSerial", ["foo"]]}], "id": "a0l976iv", "method": "ping"} print jsonrpclib.history.result # {'jsonrpc': '2.0', 'result': , 'id': 'a0l976iv'} To turn on this behaviour, just set jsonrpclib.config.use_jsonclass to True. If you want to use a different method for serialization, just set jsonrpclib.config.serialize_method to the method name. Finally, if you are using classes that you have defined in the implementation (as in, not a separate library), you'll need to add those (on BOTH the server and the client) using the jsonrpclib.config.classes.add() method. (Examples forthcoming.) Feedback on this "feature" is very, VERY much appreciated. Why JSON-RPC? ------------- In my opinion, there are several reasons to choose JSON over XML for RPC: * Much simpler to read (I suppose this is opinion, but I know I'm right. :) * Size / Bandwidth - Main reason, a JSON object representation is just much smaller. * Parsing - JSON should be much quicker to parse than XML. * Easy class passing with jsonclass (when enabled) In the interest of being fair, there are also a few reasons to choose XML over JSON: * Your server doesn't do JSON (rather obvious) * Wider XML-RPC support across APIs (can we change this? :)) * Libraries are more established, i.e. more stable (Let's change this too.) TESTS ----- I've dropped almost-verbatim tests from the JSON-RPC spec 2.0 page. You can run it with: pip install -r dev-requirements.txt nosetests tests.py TODO ---- * Use HTTP error codes on SimpleJSONRPCServer * Test, test, test and optimize jsonrpclib-0.1.7/dev-requirements.txt0000664000175000017500000000023712631226744020601 0ustar travistravis00000000000000coverage==4.0 linecache2==1.0.0 nose==1.3.7 pluggy==0.3.1 py==1.4.30 six==1.9.0 tox==2.1.1 traceback2==1.4.0 unittest2==1.1.0 virtualenv==13.1.2 wheel==0.24.0 jsonrpclib-0.1.7/setup.py0000775000175000017500000000231412631226744016254 0ustar travistravis00000000000000#!/usr/bin/env python """ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import distutils.core import os long_description = "Placeholder in case of missing README.md." if os.path.exists("README.md"): with open("README.md") as readme_fp: long_description = readme_fp.read() distutils.core.setup( name="jsonrpclib", version="0.1.7", packages=["jsonrpclib"], author="Josh Marshall", author_email="catchjosh@gmail.com", url="http://github.com/joshmarshall/jsonrpclib/", license="http://www.apache.org/licenses/LICENSE-2.0", description="This project is an implementation of the JSON-RPC v2.0 " + "specification (backwards-compatible) as a client library.", long_description=long_description) jsonrpclib-0.1.7/PKG-INFO0000664000175000017500000002447412631227001015632 0ustar travistravis00000000000000Metadata-Version: 1.0 Name: jsonrpclib Version: 0.1.7 Summary: This project is an implementation of the JSON-RPC v2.0 specification (backwards-compatible) as a client library. Home-page: http://github.com/joshmarshall/jsonrpclib/ Author: Josh Marshall Author-email: catchjosh@gmail.com License: http://www.apache.org/licenses/LICENSE-2.0 Description: [![Build Status](https://travis-ci.org/joshmarshall/jsonrpclib.svg)](https://travis-ci.org/joshmarshall/jsonrpclib) JSONRPClib ========== This library is an implementation of the JSON-RPC specification. It supports both the original 1.0 specification, as well as the new (proposed) 2.0 spec, which includes batch submission, keyword arguments, etc. It is licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html). Communication ------------- Feel free to send any questions, comments, or patches to our Google Group mailing list (you'll need to join to send a message): http://groups.google.com/group/jsonrpclib Summary ------- This library implements the JSON-RPC 2.0 proposed specification in pure Python. It is designed to be as compatible with the syntax of xmlrpclib as possible (it extends where possible), so that projects using xmlrpclib could easily be modified to use JSON and experiment with the differences. It is backwards-compatible with the 1.0 specification, and supports all of the new proposed features of 2.0, including: * Batch submission (via MultiCall) * Keyword arguments * Notifications (both in a batch and 'normal') * Class translation using the 'jsonclass' key. I've added a "SimpleJSONRPCServer", which is intended to emulate the "SimpleXMLRPCServer" from the default Python distribution. Requirements ------------ It supports cjson and simplejson, and looks for the parsers in that order (searching first for cjson, then for the "built-in" simplejson as json in 2.6+, and then the simplejson external library). One of these must be installed to use this library, although if you have a standard distribution of 2.6+, you should already have one. Keep in mind that cjson is supposed to be the quickest, I believe, so if you are going for full-on optimization you may want to pick it up. Installation ------------ You can install this from PyPI with one of the following commands (sudo may be required): easy_install jsonrpclib pip install jsonrpclib Alternatively, you can download the source from the github repository at http://github.com/joshmarshall/jsonrpclib and manually install it with the following commands: git clone git://github.com/joshmarshall/jsonrpclib.git cd jsonrpclib python setup.py install Client Usage ------------ This is (obviously) taken from a console session. >>> import jsonrpclib >>> server = jsonrpclib.Server('http://localhost:8080') >>> server.add(5,6) 11 >>> print jsonrpclib.history.request {"jsonrpc": "2.0", "params": [5, 6], "id": "gb3c9g37", "method": "add"} >>> print jsonrpclib.history.response {'jsonrpc': '2.0', 'result': 11, 'id': 'gb3c9g37'} >>> server.add(x=5, y=10) 15 >>> server._notify.add(5,6) # No result returned... >>> batch = jsonrpclib.MultiCall(server) >>> batch.add(5, 6) >>> batch.ping({'key':'value'}) >>> batch._notify.add(4, 30) >>> results = batch() >>> for result in results: >>> ... print result 11 {'key': 'value'} # Note that there are only two responses -- this is according to spec. If you need 1.0 functionality, there are a bunch of places you can pass that in, although the best is just to change the value on jsonrpclib.config.version: >>> import jsonrpclib >>> jsonrpclib.config.version 2.0 >>> jsonrpclib.config.version = 1.0 >>> server = jsonrpclib.Server('http://localhost:8080') >>> server.add(7, 10) 17 >>> print jsonrpclib..history.request {"params": [7, 10], "id": "thes7tl2", "method": "add"} >>> print jsonrpclib.history.response {'id': 'thes7tl2', 'result': 17, 'error': None} >>> The equivalent loads and dumps functions also exist, although with minor modifications. The dumps arguments are almost identical, but it adds three arguments: rpcid for the 'id' key, version to specify the JSON-RPC compatibility, and notify if it's a request that you want to be a notification. Additionally, the loads method does not return the params and method like xmlrpclib, but instead a.) parses for errors, raising ProtocolErrors, and b.) returns the entire structure of the request / response for manual parsing. SimpleJSONRPCServer ------------------- This is identical in usage (or should be) to the SimpleXMLRPCServer in the default Python install. Some of the differences in features are that it obviously supports notification, batch calls, class translation (if left on), etc. Note: The import line is slightly different from the regular SimpleXMLRPCServer, since the SimpleJSONRPCServer is distributed within the jsonrpclib library. from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer server = SimpleJSONRPCServer(('localhost', 8080)) server.register_function(pow) server.register_function(lambda x,y: x+y, 'add') server.register_function(lambda x: x, 'ping') server.serve_forever() Class Translation ----------------- I've recently added "automatic" class translation support, although it is turned off by default. This can be devastatingly slow if improperly used, so the following is just a short list of things to keep in mind when using it. * Keep It (the object) Simple Stupid. (for exceptions, keep reading.) * Do not require init params (for exceptions, keep reading) * Getter properties without setters could be dangerous (read: not tested) If any of the above are issues, use the _serialize method. (see usage below) The server and client must BOTH have use_jsonclass configuration item on and they must both have access to the same libraries used by the objects for this to work. If you have excessively nested arguments, it would be better to turn off the translation and manually invoke it on specific objects using jsonrpclib.jsonclass.dump / jsonrpclib.jsonclass.load (since the default behavior recursively goes through attributes and lists / dicts / tuples). [test_obj.py] # This object is /very/ simple, and the system will look through the # attributes and serialize what it can. class TestObj(object): foo = 'bar' # This object requires __init__ params, so it uses the _serialize method # and returns a tuple of init params and attribute values (the init params # can be a dict or a list, but the attribute values must be a dict.) class TestSerial(object): foo = 'bar' def __init__(self, *args): self.args = args def _serialize(self): return (self.args, {'foo':self.foo,}) [usage] import jsonrpclib import test_obj jsonrpclib.config.use_jsonclass = True testobj1 = test_obj.TestObj() testobj2 = test_obj.TestSerial() server = jsonrpclib.Server('http://localhost:8080') # The 'ping' just returns whatever is sent ping1 = server.ping(testobj1) ping2 = server.ping(testobj2) print jsonrpclib.history.request # {"jsonrpc": "2.0", "params": [{"__jsonclass__": ["test_obj.TestSerial", ["foo"]]}], "id": "a0l976iv", "method": "ping"} print jsonrpclib.history.result # {'jsonrpc': '2.0', 'result': , 'id': 'a0l976iv'} To turn on this behaviour, just set jsonrpclib.config.use_jsonclass to True. If you want to use a different method for serialization, just set jsonrpclib.config.serialize_method to the method name. Finally, if you are using classes that you have defined in the implementation (as in, not a separate library), you'll need to add those (on BOTH the server and the client) using the jsonrpclib.config.classes.add() method. (Examples forthcoming.) Feedback on this "feature" is very, VERY much appreciated. Why JSON-RPC? ------------- In my opinion, there are several reasons to choose JSON over XML for RPC: * Much simpler to read (I suppose this is opinion, but I know I'm right. :) * Size / Bandwidth - Main reason, a JSON object representation is just much smaller. * Parsing - JSON should be much quicker to parse than XML. * Easy class passing with jsonclass (when enabled) In the interest of being fair, there are also a few reasons to choose XML over JSON: * Your server doesn't do JSON (rather obvious) * Wider XML-RPC support across APIs (can we change this? :)) * Libraries are more established, i.e. more stable (Let's change this too.) TESTS ----- I've dropped almost-verbatim tests from the JSON-RPC spec 2.0 page. You can run it with: pip install -r dev-requirements.txt nosetests tests.py TODO ---- * Use HTTP error codes on SimpleJSONRPCServer * Test, test, test and optimize Platform: UNKNOWN