Hi, I'm Harlin and welcome to my blog. I write about Python, Alfresco and other cheesy comestibles.

Python - Intro to web.py

Intro to web.py

I was first introduced to web programming some time around 2003. At the time, the hot language/platform was PHP. I had begun to learn Python because I was interested in writing a monitoring tool which Python seemed to be well suited for. At that time, there was Python Server Pages which then was something of a minimalist Python web framework. PHP seemed to have a lot more going for it at the time but I did not want to use another language just to create websites.

Some time around then was another Python web framework called CherryPy. It was more in use than the old Python Server Pages. In fact, even today, CherryPy is a main component of TurboGears. It is fairly simple to build most anything from the ground up. For some reason, I found that I gravitated more so to Django. It could have been because of Django's ORM. I do know at the time, I was not comfortable using SQLAlchemy and wanted to focus on creating sites and not databases necessarily. So, for me, Django kind of stuck.

As things always go, we tend to revisit the past. Another minimalist Python web framework, web.py is something I have looked into recently. Why? Is it something useful for my career? Not really but it is something that interests me just from a hobbyist standpoint. Sometimes you have to do that to generate interest to keep you somewhat busy. So, what I want to introduce to you today is web.py.

web.py

From its own site, web.py is a web framework for Python that is as simple as it is powerful. web.py is in the public domain; you can use it for whatever purpose with absolutely no restrictions. It is simple to install. It is simple to build a project directory for it.

It is in use by a number of small production sites. Originally, reddit used it at some point.

What I want to do with this post is to go from ground zero of a Hello World app to show you how to run with gunicorn and handle POST data.

If you're interested in following along, let's start off with finding a directory to start our simple project.

Create a Virtual Environment

As always, I recommend using pyenv. It is by far the very best way to set up a virtual environment that can make use of its own version of Python and its own set of packages so that there won't be packaging and Python version conflicts with other projects you might have.

# mkdir webpy_demo
# cd webpy_demo
# pyenv install 3.6.4
# pyenv global 3.6.4
# pyenv virtualenv webpy_demo
# pyenv local webpy_demo

Install and Setup

I recommend ... no ... insist you use the latest Python 3 version which of now is 3.6.4. As such, we'll need to install a particular version of web.py that is compatible with 3.6.4.

Install Web.py using Python 3.6.4:

# pip install web.py==0.40.dev0

Hello World App

So, let's go ahead and create our Hello World app. This app will simply run as a web server and when you go to http://localhost:8080/ you will see Hello World! and that's it.

Open demo1.py and add the following:

#!/usr/bin/env python

import web

urls = (
    '/', 'Index',
)


class Index:
    def GET(self):
        return 'Hello World!'


if __name__ == '__main__':
    app = web.application(urls, globals())
    app.run()

Ok, now let's run our demo1.py:

# ./demo1.py

Now, go to http://localhost:8080/ and you should see Hello World!.

So, what did we do here?

import web

We'll need to import web so we can run this demo.

urls = (
    '/', 'Index',
)

We're setting up our routing so that when a user goes to / as in http://localhost:8080/ this will route our demo app to use the class called Index.

class Index:
    def GET(self):
        return 'Hello World!'

This Index class is a resource handler and allows for a GET method (remember that with http connections, one can make GET, POST, DELETE, PUT, etc. calls). So when we pull up a URL in a browser like Chrome it will make a GET request. When that happens, the code in the GET method of our Index class will be executed.

In this case, it will simply return some text as 'Hello World!'.

Simple enough, right?

Use Templates

Eventually, we will want to serve some html instead of plain text. web.py allows one to do this with templates.

Here we'll create a file called demo2.py and a template file called index.html.

Create a directory named templates:

# mkdir templates

Create an html file under templates called index.html and add the following:

$def with (name)

$if name:
    <p>Hello to $name.</p>

$else:
    <p>Hello World!</p>

Add the following code to the demo2.py file and save it:

#!/usr/bin/env python

import web

urls = (
    '/(.*)', 'Index',
)


class Index:
    def GET(self, name):
        render = web.template.render('templates/')
        return render.index(name)


if __name__ == '__main__':
    app = web.application(urls, globals())
    app.run()

Now, let's run it:

# ./demo2.py

If we pull up http://localhost:8080/ in the browser, our output will only be 'Hello World!'. But if we use the url, http://localhost:8080/Bill we should get 'Hello to Bill'.

So, how does this work? With the urls variable, we have a route:

'/(.*)' - This accepts the / and whatever comes after / as a variable called 'name'. Note that 'name' is required in the GET method's parameter. So, as in the case above with /Bill, the name variable is set to 'Bill'. Then, with render.index(name), name is passed to the template (index.html) as name.

Note that in the template, $def with (name) defines name as the value passed to it with render.index(). Then name is output with $name in the template.

Deploy with Gunicorn (wsgi)

Ok, so how can we deploy web.py? The easiest way would be using Gunicorn which handles wsgi but does not handle static content. We would use Apache or Nginx for that. To demonstrate how Gunicorn works with web.py, let's install it:

Run with gunicorn:

# pip install gunicorn

Open demo3.py and add the following:

#!/usr/bin/env python

import web

urls = (
    '/(.*)', 'Index',
)


class Index:
    def GET(self, name):
        render = web.template.render('templates/')
        return render.index(name)


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

This time we won't add the if name == 'main' directive as we won't be calling demo3.py as a standalone script. We'll do something else instead.

From the command line:

# gunicorn -w 4 -b 127.0.0.1:8080 demo3:wsgiapp

Now, you can go to http://localhost:8080/ and see that Gunicorn serves this up just fine.

Use POST for User Input

Using GET requests are great but eventually, we'll need to be able to handle user input in forms as well. Below, I'll show you how to write code to handle POST requests.

Open demo4.py and add the following:

#!/usr/bin/env python

import web

urls = (
    '/', 'Index',
)


class Index:
    def GET(self):
        render = web.template.render('templates/')
        return render.getname()

    def POST(self):
        model = web.input()
        render = web.template.render('templates/')
        return render.index(model.name)


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

Create new templates called getname.html:

<!DOCTYPE html>
<html>
<head>
    <title>Get Name</title>
</head>
<body>
    <h2>What is your name?</h2>
    <form action="" method="post">
        <p>Enter name:</p>
        <p>
            <input type="text" name="name" />
        </p>
        <p>
            <input type="submit" value="Enter" />
        </p>
    </form>
</body>
</html>

Run gunicorn:

# gunicorn -w 4 -b 127.0.0.1:8080 demo4:wsgiapp

Now, when you go to http://localhost:8080/ you will have an html form to handle the input of your name. When you enter your name and press the Enter button, the app will greet you by name on the index.html page.

Static Content

Be aware that static content should be handled by a web server like Apache or Nginx. But, in the case of development we can make use of a static handler like the following.

Open demo5.py and add the following code:

#!/usr/bin/env python

import web

urls = (
    '/(.*)', 'Index',
)


class Index:
    def GET(self, name):
        render = web.template.render('templates/')
        return render.showicon()


if __name__ == '__main__':
    app = web.application(urls, globals())
    app.run()

So far, there's nothing too unusual. In fact our Python code is still about the same as we used in demo2.py. What we hope to do here is to display an icon as an image.

Let's create a standard directory structure for images and we'll download web.py's very own icon to display on our page:

# mkdir -p static/img
# cd static/img
# wget http://webpy.org/static/webpy.gif

Next, create an html file in templates dir called showicon.html and add the following:

<!DOCTYPE html>
<html>
<head>
    <title>Show Icon</title>
</head>
<body>
    <p>
        <img src="/static/img/webpy.gif">
    </p>
</body>
</html>

Now we can run at the command line our demo5.py script:

# ./demo5.py

When we go to http://localhost:8080/ we will see the icon served up as static content.

Conclusion

Hopefully this gives you a taste of Python minimalist web frameworks. Honestly, CherryPy, Flask and a few others do not work much differently than web.py. If you are having trouble learning some of the heavier Python web frameworks like Django, TurboGears or Pylons, I suggest considering learning something that is more of a microframework. Or, if you are bored and want to play around with something more simple, then web.py might be of interest to you.

Happy Pythoning! -H.S.

Any Comments, Always Welcome!