python Python WSGI, the first steps

Python

Building web apps in python is quite a lot of fun, and so I decided to write a little micro-framework from scratch. Today we will only think about interface with the webserver, using WSGI.

WSGI is the de-facto standard to interface a web application with a server (also known as "gateway"). You may read the PEP 3333 to learn more about it, but here I want to go straight to building a little wsgi app.

WSGI applications have an onion architecture. The application is in the middle, the webserver is outside, and a certain number of middlewares inbetween can gives your application extra super-powers.

Get your environment ready

To build our application, we'll use a virtual environment to isolate our experiments.

Let's install some packages. Depending on your system, python-pip may be not available and python-setuptools will replace it well (you'll need to use easy_install instead of pip to install python packages, but this is outside the scope of this blog post).

    $ sudo apt-get install python-virtualenv python python-pip build-essential libevent-dev python-dev
    $ mkdir wsgi-test
    $ cd wsgi-test
    $ virtualenv --no-site-packages wsgi-test.env
    $ . wsgi-test.env/bin/activate
    $ pip install webob weberror gevent

This article has been written using python 2.6, but should be ok for python>=2.5. It probably needs some adjustements for python>=3.0

First application

A WSGI application is basically a python callable which takes some parameters and returns an iterator on the response body parts. The input parameters are an environment dict, and a start_response callable which you'll call to start sending the headers.

Let's write the first app.

def wsgi_app(environ, start_response):
    start_response('200 OK', [])
    return ['Hello, little world.']

if __name__ == '__main__':
    from gevent import wsgi
    wsgi.WSGIServer(('', 2222), wsgi_app, spawn=None).serve_forever()

This app is a bit minimalist, but it works. You can run the file and check http://localhost:2222/.

We're using gevent to embed a webserver in our application because it's fast and easy. Almost every webserver have WSGI support, either builtin or using extensions, so you can switch to whatever you like. Using Apache for example, you can run your application using mod_wsgi. Configuring a web server is out of this article's scope.

Getting programmer friendly

Of course, it's always annoying to talk raw WSGI. You will forget some headers, behave badly, kill people around you ... But we have installed a tool that will transform the environ dict into a nicely formated python object.

Let me introduce my good friend webob.

from webob import ( Request as HttpRequest,
                    Response as HttpResponse )

def wsgi_app(environ, start_response):
    # Create request from WSGI environment.
    request = HttpRequest(environ=environ)

    # A real app would look at the request before giving blind answers.

    # Create a response
    response = HttpResponse('Hello, better world.')

    # Communicate
    start_response(response.status, response.headerlist)
    return response.body

if __name__ == '__main__':
    from gevent import wsgi
    wsgi.WSGIServer(('', 2222), wsgi_app, spawn=None).serve_forever()

This is a bit better. But hey, we can then separate the WSGI stuff, and the request-to-response process.

from webob import ( Request as HttpRequest,
                    Response as HttpResponse )

def web_app(request):
    return HttpResponse('Hello, nice world at %s.' % request.path_info)

def wsgi_app(environ, start_response):
    # Create the request
    request = HttpRequest(environ=environ)

    # Build a response from this request
    response = web_app(request)

    # Communicate with gateway
    start_response(response.status, response.headerlist)
    return response.body

if __name__ == '__main__':
    from gevent import wsgi
    wsgi.WSGIServer(('', 2222), wsgi_app, spawn=None).serve_forever()

Pythonize it

The good news is that the webob library includes a decorator that is able to turn any request-taking/response-returning python callable in a WSGI-capable callable.

The last version of this program, which does nothing more than the last one, follows.

from webob import Response as HttpResponse
from webob.dec import wsgify

@wsgify
def app(request):
    return HttpResponse('Hello, short world at %s.' % request.path_info)

if __name__ == '__main__':
    from gevent import wsgi
    wsgi.WSGIServer(('', 2222), app, spawn=None).serve_forever()

This is all for today, but there is a lot more to come so stay tuned.

Share the love!

Liked this article? Please consider sharing it on your favorite network, it really helps me a lot!

You can also add your valuable insights by commenting below.