How to Write Node.js Applications in Python using PythonJS on OpenShift

Today I came across an interesting library called PythonJS that converts Python to JavaScript. It converts to JavaScript and generates JavaScript, CoffeeScript, and Dart code for the given Python code. In this blog you will learn how you can use PythonJS to deploy Python’s Tornado web framework application to OpenShift’s Node 0.10 cartridge.

Github Repository

The code for today’s demo application is available on github: june-2014-blog-pythonjs.

Prerequisites

  1. Sign up for an OpenShift Account. It is completely free and Red Hat gives every user three free gears on which to run your applications. At the time of writing, the combined resources allocated for each user is 1.5GB of memory and 3GB of disk space.

  2. Install the RHC client tool on your machine. RHC is a Ruby Gem, so you need to have Ruby 1.8.7 or above on your machine. To install RHC, just type sudo gem install rhc on the command line. If you already have the RHC Gem installed, make sure it is the latest one. To update RHC, execute the command sudo gem update rhc. For more assistance setting up the RHC command-line tool, see the following page: https://openshift.redhat.com/community/developers/rhc-client-tools-install.

  3. Set up your OpenShift account using the command rhc setup. This command helps you create a namespace and upload your SSH key to the OpenShift server.

Step 1: Create the OpenShift Node.js Application

To create a Node.js 0.10 application on OpenShift, type the command shown below.

Note: Be sure to use a medium or large sized gear. This application will not work with a small gear as PythonJS is very CPU and memory intensive.

$ rhc app create pythonjs nodejs-0.10 --gear large

This command creates an application container for us, called a gear, and sets up all of the required SELinux policies and Cgroup configurations. Next, it installs all of the required software on your gear. OpenShift also sets up a private Git repository with some template code and clones the repository to your local system. Finally, OpenShift propagates DNS updates to the outside world.

Step 2: Delete the template application source

The template application created by OpenShift is an Express web framework application. Since you are writing the Node application in Python, you can delete the template code by running the command shown below.

$ rm -rf deplist.txt index.html node_modules/ package.json server.js 

Step 3: Create package.json and download required libraries

The package.json file is a JSON file that is used to specify the Node’s application metadata and its dependencies. Create a new file called package.json in the application root and add the following contents to it.

{
    "name":"PythonJSApp",
    "description":"My First PythonJS Application",
    "version":"0.0.1"
}

This application uses PythonJS which converts Python code to Node,js code. Before you can use PythonJS, you have to install it. Run the following command to install the required library.

$ npm install --save python-js ws

This downloads the required dependencies and populate the package.json with the python-js and ws dependencies.

Step 4: Create the Python Tornado Application

Create a new file called app.py in the application root and paste the following content into it.

import tornado, tornado.web, tornado.ioloop
import os
 
PATHS = { 'webroot': './static'}
 
def get_main_page(server):
    local_path = os.path.join( PATHS['webroot'], "index.html" )
    data = open(local_path, 'r').read()
    data = convert_python_html_document( data )
    return data
 
def convert_python_html_document( data ):
    doc = list()
    script = None
    for line in data.splitlines():
 
        if line.strip().startswith('<script'):
            if 'type="text/python"' in line:
                doc.append( '<script type="text/javascript">')
                script = list()
            else:
                doc.append( line )
 
        elif line.strip() == '</script>':
            if script:
                #src = '\n'.join( script ) 
                src = chr(10).join(script)
                js = pythonjs.translator.to_javascript( src )
                doc.append( js )
            doc.append( line )
            script = None
 
        elif isinstance( script, list ):
            script.append( line )
 
        else:
            doc.append( line )
 
    return '\n'.join( doc )
 
 
 
class MainHandler( tornado.web.RequestHandler ):
    def get(self, path=None):
        print('path', path)
 
        if path == "":
            data = get_main_page()
            self.set_header("Content-Type", "text/html; charset=utf-8")
            self.write( get_main_page() )
        elif path == 'pythonjs.js':
            data = pythonjs.runtime.javascript
            self.set_header("Content-Type", "text/javascript; charset=utf-8")
            self.set_header("Content-Length", len(data))
            self.write(data)
        else:
            if path == 'favicon.ico':
                self.write('')
            else:
                self.write('File not found')
 
 
handlers = [
    ('/', MainHandler)
]
 
app = tornado.web.Application( handlers )
ip   = 'localhost'
port = 8080
if os.environ.get('OPENSHIFT_NODEJS_IP'):
    ip = os.environ.get('OPENSHIFT_NODEJS_IP')
    port = int(os.environ.get('OPENSHIFT_NODEJS_PORT'))
 
app.listen(port,ip)
tornado.ioloop.IOLoop.instance().start()
print('App running on %s:%s' % (ip,port))

The code shown above does the following:

  1. Imports all of the required libraries.

  2. Defines a new class called MainHandler that extends web.RequestHandler. A Tornado web applications maps URLs or URL patterns to subclasses of the web.RequestHandler class. These classes define get(), post(), etc. methods to handle HTTP GET or POST requests to that URL. When a GET request is made to the ‘/’ url, then MainHandler responds with index.html.

  3. Creates the Tornado application instance by passing it the handlers array.

  4. Starts the application using the start() function.

Step 5: Create the view

Create a new directory called static inside the application root. Create a file named index.html inside the static directory and paste the following content into it.

<html>
<head>
<script src="pythonjs.js"></script>
 
<script type="text/python">
 
a = 'hello'
b = 'world'
 
def test():
    print(a+b)
 
    con = document.createElement( 'div' )
    con.setAttribute('id', 'mydiv')
    document.body.appendChild(con)
    print( con )
    txt = document.createTextNode( a+b )
    con.appendChild(txt)
 
    print( con.getAttribute('id') )
    btn = document.getElementById('mybutton')
    print(btn)
    btn.firstChild.nodeValue = 'CLICKED!'
 
 
</script>
</head>
 
<body>
<button id="mybutton" onclick="test()">click me</button>
</body>
</html>

The Python code inside <script type="text/python"> is converted to JavaScript by PythonJS.

Step 6: Create the PythonJS runner

To run this application, you need to create a runner that converts the Python application to a Node.js application. Create a new file called server.js and place the following content into it. It translates the Tornado API into the NodeJS http API.

#!/usr/bin/env node
var fs = require('fs')
var pythonjs = require('python-js')
var pycode = fs.readFileSync( './app.py', {'encoding':'utf8'} )
var jscode = pythonjs.translator.to_javascript( pycode )
eval( pythonjs.runtime.javascript + jscode )

Step 7: Deploying to the Cloud

If we push the code to OpenShift, it will not work because the PythonJS Tornado bindings do not provide IP address configuration. I have created a pull request with a fix. For the time being, open the tornado.py file in the node_modules/python-js/fakelibs directory and update the listen function with the one shown below. In case you get indentation errors then please copy the code from Github repository.

def listen(self, port, address=""):
        print 'listening on:', port
 
        server = self[...]
 
        if self._ws_handler:
            options = {
                'server' : server,
                'path' : self._ws_path
            }
            with javascript:
                wss = new( __ws.Server(options[...]) )
            print 'wss', wss
            self.wss = wss
            self.wss.on('connection', self.on_ws_connection)
 
        server.listen( port, address )

Now commit the code to the local git repository and then push the changes to to OpenShift. Please note that we are also committing the node_modules directory. This is required otherwise OpenShift will download the dependencies using npm and our change to the tornado binding won’t be deployed.

$ git add .
$ git commit -am "PythonJS application"
$ git push

After the code is pushed, and the application is deployed on OpenShift, we can view the running application at http://pythonjs-{domain-name}.rhcloud.com.

Next Steps

Automatic Updates

Stay informed and learn more about OpenShift by receiving email updates.

Categories
Node.js, OpenShift Origin, Python
Tags
Comments are closed.