Lately I’ve been writing code
using WebOb and just a few other
small libraries. It’s not
entirely obvious what this looks
like, so I thought I’d give a
simple example.
I make each application a class.
Instances of the class are
"configured applications".
So it looks a little like this (for
an application that takes one
configuration parameter,
file_path):
class
Application(object):
def
__init__(self,
file_path):
self.file_path
= file_path
Then the app needs to be a WSGI app,
because that’s how I roll. I
use
webob.dec:
from
webob.dec
import
wsgify
from webob
import
exc
from webob
import
Response
class
Application(object):
def
__init__(self,
file_path):
self.file_path
= file_path
@wsgify
def
__call__(self,
req):
return
Response('Hi!')
Somewhere separate from the
application you actually instantiate
Application. You can use
Paste Deploy
for that, configure it yourself, or
just do something ad hoc (a lot of
mod_wsgi
.wsgi
files are like this, basically).
I use
webob.exc
for things like
exc.HTTPNotFound(). You can raise that as an
exception, but I mostly just return
the object (to the same effect).
Now you have Hello World. I then
sometimes do something terrible, I
start handling URLs like this:
@wsgify
def
__call__(self,
req):
if
req.path_info
==
'/':
return
self.index(req)
elif
req.path_info.startswith('/view/'):
return
self.view(req)
return
exc.HTTPNotFound()
This is lazy and a very bad idea. So
you want a dispatcher. There are
several (e.g.,
selector). I’ll use
Routes
here… the latest release makes
it a bit easier (though it could
still be streamlined a bit).
Here’s a pattern I think makes
sense:
from routes
import
Mapper
class
Application(object):
map =
Mapper()
map.connect('index',
'/',
method='index')
map.connect('view',
'/view/{item}', method='view')
def
__init__(self,
file_path):
self.file_path
= file_path
@wsgify
def
__call__(self,
req):
results
=
self.map.routematch(environ=req.environ)
if
not
results:
return
exc.HTTPNotFound()
match,
route = results
link =
URLGenerator(self.map, req.environ)
req.urlvars
= ((),
match)
kwargs =
match.copy()
method =
kwargs.pop('method')
req.link =
link
return
getattr(self,
method)(req,
**kwargs)
def
index(self,
req):
...
def
view(self, req,
item):
...
Another way you might do it is to
skip the class, which means skipping
a clear place for configuration. I
don’t like that, but if you
don’t care about that, then it
looks like this:
def
index(self,
req):
...
def
view(self, req,
item):
...
map =
Mapper()
map.connect('index',
'/',
view=index)
map.connect('view',
'/view/{item}', view=view)
@wsgify
def
application(req):
results =
map.routematch(environ=req.environ)
if
not
results:
return
exc.HTTPNotFound()
match, route =
results
link =
URLGenerator(map,
req.environ)
req.urlvars
= ((),
match)
kwargs = match.copy()
view = kwargs.pop('view')
req.link
= link
return
view(req, **kwargs)
Then
application
is pretty much boilerplate. You
could put configuration in the
request if you wanted, or use some
other technique (like
Contextual).
I talked some with
Ben Bangert
about what he’s trying with
these patterns, and he’s doing
something reminiscent of Pylons
controllers (but without the rest of
Pylons) and it looks more like this
(with my own adaptations):
class
BaseController(object):
special_vars =
['controller', 'action']
def
__init__(self,
request, link, **config):
self.request
= request
self.link
= link
for name,
value
in
config.items():
setattr(self,
name, value)
def
__call__(self):
action =
self.request.urlvars.get('action',
'index')
if
hasattr(self,
'__before__'):
self.__before__()
kwargs =
req.urlsvars.copy()
for attr
in
self.special_vars
if attr
in
kwargs:
del
kwargs[attr]
return
getattr(self,
action)(**kwargs)
class
Index(BaseController):
def
index(self):
...
def
view(self,
item):
...
class
Application(object):
map =
Mapper()
map.connect('index',
'/',
controller=Index)
map.connect('view',
'/view/{item}', controller=Index,
action='view')
def
__init__(self,
**config):
self.config
= config
@wsgify
def
__call__(self,
req):
results
=
self.map.routematch(environ=req.environ)
if
not
results:
return
exc.HTTPNotFound()
match,
route = results
link =
URLGenerator(self.map, req.environ)
req.urlvars
= ((),
match)
controller = match['controller'](req,
link, **self.config)
return
controller()
That’s a lot of code blocks,
but they all really say the same
thing ;) I think writing apps with
almost-no-framework like this is
pretty doable, so if you have
something small you should give it a
go. I think it’s especially
appropriate for applications that
are an API (not a "web
site").