JsonRpc

This is an easy-to-use implementation of a JSON-RPC handler for Django. (A second alternative implementation which is even easier to use - but has less configuration options - is also included, below)

Features:



Example Django Usage

myproject/myapp/views.py:

from jsonrpc import JsonRpc, publicmethod
from django.core.urlresolvers import reverse
from django.http import HttpResponse

class MyRpcMethods(object):

    url = reverse("myapp-rpc")

    @publicmethod
    def add(x, y):
        return x+y

    @publicmethod
    def sub(x, y):
        return x-y

    def read_diary(self):
        return "Here's all my secrets ..."

    @publicmethod
    def find_person(attribs):

        filters = dict((key, val) for key, val in attribs.items()
                       if key in ("first_name", "last_name"))

        return [
            {"first_name": p.first_name, "last_name": p.last_name} \
            for p in Person.objects.filter(**filters)
        ]

     @publicmethod
     def sayHello(*args):
         return "hello "

# set the urls /myapp/rpc/ to this myapp.views.my_rpc_view and give it a name='myapp-rpc'
# looks like this url(r'^/myapp/rpc/$', 'myapp.views.my_rpc_view', name='myapp-rpc'),
# i cannot use the result give by this example, i use 
# return HttpResponse(result, mimetype='application/json')
def my_rpc_view(request):

    rpc     = JsonRpc( MyRpcMethods() )
    result  = rpc.handle_request(request)

    return result

Example Javascript client usage

var rpc = new dojo.rpc.JsonService("/myapp/rpc/");

rpc.add(3,4).addCallback(function(result) {
    console.log(result);
});
>>> 7

rpc.callRemote("sub", [9,5]).addCallback(function(result) {
    console.log(result);
});
>>> 4

rpc.callRemote("read_diary").addCallback(function(secrets) {
    console.log(secrets);
});
>>> no such method

rpc.find_person({last_name: "Baggins"}).addCallback(function(result) {
    dojo.forEach(result, function(person) {
      console.log("Found: " + person.first_name, person.last_name);
    });
});
>>> Found: Bilbo Baggins
>>> Found: Frodo Baggins

JsonRpc

jsonrpc.py

#
# Copyright (c) 2009, Ben Wilber (benwilber@gmail.com)
# All rights reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License. You should have
# received a copy of the GPL license along with this program; if you
# did not, you can find it at http://www.gnu.org/
#

class publicmethod(object):

    def __init__(self, method):
        self.method = method
        __public__  = True

    def __call__(self, *args, **kwargs):
        return self.method(*args, **kwargs)

    def get_args(self):
        from inspect import getargspec
        return [ a for a in getargspec(self.method).args if a != "self" ]

class JsonRpc(object):

    def __init__(self, instance, allow_errors=True, report_methods=True):

        self.instance            = instance
        self.allow_errors        = allow_errors
        self.report_methods        = report_methods

        if not hasattr(self.instance, "url"):
            raise Exception("'url' not present in supplied instance")

    def get_public_methods(self):

        return [
            m for m in dir(self.instance) if \
            getattr(self.instance, m).__class__.__name__ == "publicmethod" and \
            getattr(self.instance, m).__public__ == True
        ]

    def generate_smd(self):

        smd = {
            "serviceType": "JSON-RPC",
            "serviceURL": self.instance.url,
            "methods": []
        }

        if self.report_methods:
            smd["methods"] = [
                {"name": method, "parameters": getattr(self.instance, method).get_args()} \
                for method in self.get_public_methods()
            ]

        return simplejson.dumps(smd)
 
    def dispatch(self, method, params):

        if hasattr(self.instance, "dispatch") and \
            callable(self.instance.dispatch):
            return self.instance.dispatch(method, params)
        elif method in self.get_public_methods():
            return getattr(self.instance, method)(*params)
        else:
            return "no such method"

    def serialize(self, raw_post_data):

        raw_request        = simplejson.loads(raw_post_data)
        request_id        = raw_request.get("id", 0)
        request_method    = raw_request.get("method")
        request_params    = raw_request.get("params", [])

        response        = {"id": request_id}

        try:
            response["result"] = self.dispatch(request_method, request_params)
        except:
            if self.allow_errors:
                from sys import exc_type, exc_value
                response["error"] = "%s: %s" % (exc_type, exc_value)
            else:
                response["error"] = "error"

        return simplejson.dumps(response)

    def handle_request(self, request):

        if request.method == "POST" and \
            len(request.POST) > 0:
            return self.serialize(request.raw_post_data)
        else:
            return self.generate_smd()

Alternative JsonRpc Implementation

This is the proven and working JSON-RPC Django handler developed incrementally since 2007 and deployed in production Pyjamas Web 2.0 applications. It is much shorter, simpler, easier to use and less cumbersome than the above, although it does not have support for GET (to view a list of methods) and does not have the same level of configurability. Importantly, the original request object is passed to all functions in the JSONRPC service, thus allowing the application to gain access to Django session information, and cookies etc.

Usage examples are included inline in the source. Note in particular that the JSONRPCService class instance itself is handed to urlpatterns, and so all POST requests result in JSONRPCService's __call__ method being called, resulting in a much simpler approach than the above.

This code is maintained at http://groups.google.com/group/pyjamas-dev/files and a version is included in http://pyjs.org

# jsonrpc.py
#   original code: http://trac.pyworks.org/pyjamas/wiki/DjangoWithPyJamas
#   also from: http://www.pimentech.fr/technologies/outils
from django.utils import simplejson
import sys

# JSONRPCService and jsonremote are used in combination to drastically
# simplify the provision of JSONRPC services.  use as follows:
#
# from jsonrpc import JSONRPCService, jsonremote
#
# jsonservice = JSONRPCService()
#
# @jsonremote(jsonservice)
# def test(request, echo_param):
#     return "echoing the param back: %s", echo_param
#
# then dump jsonservice into urlpatterns:
#  (r'^service1/$', 'djangoapp.views.jsonservice'),

def response(id, result):
    return simplejson.dumps({'version': '1.1', 'id':id,
                             'result':result, 'error':None})
def error(id, code, message):
    return simplejson.dumps({'id': id, 'version': '1.1',
                             'error': {'name': 'JSONRPCError',
                                       'code': code,
                                       'message': message
                                       }
                                 })

class JSONRPCService:
    def __init__(self, method_map={}):
        self.method_map = method_map

    def add_method(self, name, method):
        self.method_map[name] = method

    def __call__(self, request, extra=None):
        # We do not yet support GET requests, something pyjamas does
        # not use anyways.
        data = simplejson.loads(request.raw_post_data)
        # Altered to forward the request parameter when a member method
        # is invoked <julien@pimentech.net>
        id, method, params = data["id"],data["method"],[request,]+data["params"]
        if method in self.method_map:
            try:
                result = self.method_map[method](*params)
                return response(id, result)
            except BaseException:
                etype, eval, etb = sys.exc_info()
                return error(id, 100, '%s: %s' %(etype.__name__, eval))
            except:
                etype, eval, etb = sys.exc_info()
                return error(id, 100, 'Exception %s: %s' %(etype, eval))
        else:
            return error(id, 100, 'method "%s" does not exist' % method)


def jsonremote(service):
    """Make JSONRPCService a decorator so that you can write :
    
    from jsonrpc import JSONRPCService
    chatservice = JSONRPCService()

    @jsonremote(chatservice)
    def login(request, user_name):
        (...)
    """
    def remotify(func):
        if isinstance(service, JSONRPCService):
            service.add_method(func.__name__, func)
        else:
            emsg = 'Service "%s" not found' % service.__name__
            raise NotImplementedError, emsg
        return func
    return remotify


Last modified 13 years ago Last modified on Jul 20, 2011, 12:53:41 AM
Note: See TracWiki for help on using the wiki.
Back to Top