Ajax with Python - Combining PyJS and Appengine with json-rpc

3 min read Original article ↗

I recently (re)discovered pyjs  - also called Pyjamas - which  is a tool to support development of (client-side) Ajax applications with Python, it does that by compiling Python code to Javascript (Pyjamas is inspired by GWT  - which supports writing Ajax applications in Java).

pyjs' kitchensink comes with a JSON-RPC example, this posting shows how to use Appengine to serve as a JSON-RPC server for the pyjs json-rpc example.

(Rough) Steps:
1) download appengine SDK and create an appengine application (in the dashboard)
2) download pyjs 
3) Replace pyjs example/jsonrpc/JSONRPCExample.py with this code

from ui import RootPanel, TextArea, Label, Button, HTML, VerticalPanel, HorizontalPanel, ListBox

from JSONService import JSONProxy


class JSONRPCExample:
    def onModuleLoad(self):
        self.TEXT_WAITING = "Waiting for response..."
        self.TEXT_ERROR = "Server Error"
        self.remote_py = UpperServicePython()
        self.status=Label()
        self.text_area = TextArea()
        self.text_area.setText(r"Please uppercase this string")
        self.text_area.setCharacterWidth(80)
        self.text_area.setVisibleLines(8)
        self.button_py = Button("Send to Python Service", self)
        buttons = HorizontalPanel()
        buttons.add(self.button_py)
        buttons.setSpacing(8)
        info = r'This example demonstrates the calling of appengine upper(case) method with JSON-RPC from javascript (i.e. Python code compiled with pyjs to javascript).'
        panel = VerticalPanel()
        panel.add(HTML(info))
        panel.add(self.text_area)
        panel.add(buttons)
        panel.add(self.status)
        RootPanel().add(panel)


    def onClick(self, sender):
        self.status.setText(self.TEXT_WAITING)
        text = self.text_area.getText()
        if self.remote_py.upper(self.text_area.getText(), self) < 0:
            self.status.setText(self.TEXT_ERROR)


    def onRemoteResponse(self, response, request_info):
        self.status.setText(response)


    def onRemoteError(self, code, message, request_info):
        self.status.setText("Server Error or Invalid Response: ERROR " + code + " - " + message)


class UpperServicePython(JSONProxy):
    def __init__(self):
        JSONProxy.__init__(self, "/json", ["upper"])


4) Use the following code for the appengine app

from google.appengine.ext import webapp

from google.appengine.ext.webapp.util import run_wsgi_app

import logging

from django.utils import simplejson

class JSONHandler(webapp.RequestHandler):

  def json_upper(self,args):

    return [args[0].upper()]

  def post(self):

    args = simplejson.loads(self.request.body)

    json_func = getattr(self, 'json_%s' % args[u"method"])

    json_params = args[u"params"]

    json_method_id = args[u"id"]

    result = json_func(json_params)

    # reuse args to send result back

    args.pop(u"method")

    args["result"] = result[0]

    args["error"] = None # IMPORTANT!!

    self.response.headers['Content-Type'] = 'application/json'

    self.response.set_status(200)

    self.response.out.write(simplejson.dumps(args))

application = webapp.WSGIApplication(

                                     [('/json', JSONHandler)],

                                     debug=True)

def main():

  run_wsgi_app(application)

if __name__ == "__main__":

  main()

5) compile pyjs code in 3) and create static dir in appengine app to store compiled code (i.e in app.yaml)

6) use dev_appserver.py to test locally or appcfg.py to deploy on appengine

Facebook application
By using this recipe it was easy to create a facebook app of the example in this posting - check it out at http://apps.facebook.com/testulf/

Alternative backend - using webpy

This shows how to use webpy as backend, just put the javascript/html resulting from pyjs compile into the static/ directory. Very similar as the previous approach, with the code in blue being the main diff (i.e. webpy specific)

import web

import json

urls = (

'/json', 'jsonhandler'

)

app = web.application(urls, globals())

class jsonhandler:

  def json_upper(self,args):

    return [args[0].upper()]

  def json_markdown(self,args):

    return [args[0].lower()]

  def POST(self):

    args = json.loads(web.data())

    json_func = getattr(self, 'json_%s' % args[u"method"])

    json_params = args[u"params"]

    json_method_id = args[u"id"]

    result = json_func(json_params)

    # reuse args to send result back

    args.pop(u"method")

    args["result"] = result[0]

    args["error"] = None # IMPORTANT!!

    web.header("Content-Type","text/html; charset=utf-8")

    return json.dumps(args)

if __name__ == "__main__":

  app.run()