An Introduction to Web Programming with WSGI

Talk given at:EuroPython 2007
By:Michele Simionato
Organization: StatPro Italy
Date: 2007-07-11

Introduction

This is as a talk for beginners, only knowledge of CGI and a bit of HTTP protocol is expected (non-beginners -> "Zope on a Paste")

Ok, now about me

What I have done

WSGI

Scotch_Whisky_(aka).png

Short history of WSGI

WSGI key concepts

  1. WSGI application:

    (env, resp) -> chunks of text

    env = environment dictionary of the server; resp = function sending to the client the HTTP headers

  2. WSGI middleware:

    WSGI app -> enhanced WSGI app

Hello World

from wsgiref import simple_server

def app(env, resp):
    resp(
     '200 OK', [('Content-type', 'text/html')])
    return ['<h1>Hello, World!</h1>']

server=simple_server.make_server('', 8000, app)
server.serve_forever()

A real-life example

Let me show a real problem we had at StatPro

badpricehistory.png

The history plotter

It was easy to write a simple command line history plotter

Going on the Web

Without a framework

WSGI is the answer!

The web plotter

$ python webplotter.py

Click here for the live demonstration

Some code (I)

def app(env, resp):
 form = getformdict(env)
 if form.get('submitted'):
   try:
    fname = make_graph(form.get('code'), batch=True)
   except Exception, e:
    resp('500 ERR', [('Content-type', 'text/plain')])
    return [traceback.format_exc()]
   else:
    resp('200 OK', [('Content-type', 'image/png')])
    return file(fname)

Some code (II)

else:
 resp('200 OK', [('Content-type', 'text/html')])
 return [
  'Try values such as <pre>fri-gb;AVE</pre>',
  '<pre>fri-gb;TSCO</pre> <pre>fri-us;DELL</pre>',
  '<form>', 'insert code ',
  '<input type="text" name="code"/>',
  '<input type="submit", name="submitted",'
  ' value="submit" />',
  '</form>']

Some code (III)

def getformdict(env):
   qs = env.get('QUERY_STRING')
   if qs:
       return dict((k, v[0])
              for k, v in cgi.parse_qsl(qs))
   else:
       return {}

WSGI vs. CGI

A common objection

Object publishing (I)

class Example(object):
 def __init__(self, sitename):
  self.sitename = sitename
 def __call__(self):
  yield '<h1>%s: index page</h1>' % self.sitename
  yield 'goto <a href="./page1">page1</a><br/>'
  yield 'goto <a href="./page2">page2</a><br/>'
  yield 'goto <a href="subsite">subsite</a><br/>'
 def page1(self): yield 'page1'
 def page2(self): yield 'page2'
 page1.exposed = page2.exposed = True

Object publishing (II)

class WSGIObjectPublisher(object):
 def __init__(self, root):
  self.root = root
 def __call__(self, env, resp):
  return self.getsubpage(self.root,env,resp)()
 def getsubpage(self, root, env, resp):
  script_name = util.shift_path_info(env)
  if not script_name: # We've arrived!
    resp('200 OK',[('content-type','text/html')])
    return root
  ...

Object publishing (III)

try:
 page = getattr(root, script_name)
except AttributeError:
 resp('404 Not Found',[('content-type','text/plain')])
 return lambda:['missing page %r'%script_name]
exposed = getattr(page, 'exposed', False)
if not exposed:
 resp('404 Not Found',[('content-type','text/plain')])
 return lambda : [
  '%r is not exposed!' % script_name]
return self.getsubpage(page, env, resp)

WSGI vs. frameworks

Pro:

WSGI vs. frameworks

Contra:

And now middleware

No middleware in the standard library, but lots of useful middleware from third party sources. For instance, authentication middleware:

from paste.auth.basic import AuthBasicHandler

def only_for_pippo(env, user, passwd):
    return user == 'pippo'

auth_app = AuthBasicHandler(
    app, 'app realm', only_for_pippo)

Debugging WSGI apps

from wsgiref.simple_server import make_server
from paste.evalexception import EvalException

a, b = 1,0

def app(env, resp):
  resp('200 OK',[('Content-type','text/html')])
  return [str(a/b)]

make_server('',9090,EvalException(app)
 ).serve_forever()

Show evalexception

References

That's all, folks!

(P.S. at StatPro, we are hiring! ;)