This post originated from an RSS feed registered with Python Buzz
by Carlos de la Guardia.
Original Post: WSGI songlist application: the repoze.bfg take
Feed Title: I blog therefore I am
Feed URL: http://blog.delaguardia.com.mx/feed.atom
Feed Description: A space to put my thoughts into writing.
The main idea was that this kind of application can be a lot faster than fully featured web frameworks can produce, so for machine to machine communication you have a lot to gain by dumping the web framework and using pure WSGI.
After his posting, it was fun to see a good number of web framework advocates post about how the application could be built using their framework and what advantages over pure WSGI it offered. You can see examples of the very simple songlist application that Eric described developed using CherryPY, Restish, Werkzeug and Grok. It's very educational.
I am a little late to the party, but I would like to show how repoze.bfg can do this. repoze.bfg is inspired by Zope and uses its libraries heavily, but does not require you to know anything about that. I think it's a nice little framework and fun to work with, so that's why I decided to do the songlist application with it. By the way, it would be cool to see Django and Pylons examples as well. Any takers?
On to the repoze,bfg application. First, we create a project:
paster create -t bfg_starter songlist
Next, we replace models.py with this code:
from collections import defaultdict
class SongList(object):
def __init__(self):
self.songs = defaultdict(int)
root = SongList()
def get_root(environ):
return root
And views.py has this content:
from webob import Response
from webob.exc import HTTPMethodNotAllowed
def view_songlist(context, request):
res = ','.join(['%s=%s' % (k, v) for k, v in context.songs.iteritems()])
return Response(res)
def clear_songlist(context, request):
context.songs.clear()
return Response('OK')
def view_song(context, request):
song = request.subpath[0]
return Response(str(context.songs[song]))
def update_song(context, request):
song = request.subpath[0]
context.songs[song] += 1
return Response(str(context.songs[song]))
def method_not_allowed(context, request):
return HTTPMethodNotAllowed()
Now, if you have that very common ailment know as XML allergy, you will surely hate the part where we connect views with models, but please, don't stop reading yet:
Yes, repoze.bfg uses Zope's XML configuration language, ZCML. We actually like it, but what would you expect from long time Zope users? We freely admit our minds may be warped.
If you can live with that, you'll see it's not that painful, but if you get truly sick by looking at XML or simply like to hear yourself say 'convention over configuration' every now an then, the repoze.bfg.convention package can keep you away from ZCML. Just have someone edit the ZCML in your project to add these lines:
There, now you don't have to even acknowledge the .zcml file is in your project. Just change views.py to look like this:
from webob import Response
from webob.exc import HTTPMethodNotAllowed
from repoze.bfg.interfaces import IGETRequest, IPOSTRequest, IDELETERequest
from repoze.bfg.convention import bfg_view
@bfg_view(request_type=IGETRequest)
def view_songlist(context, request):
res = ','.join(['%s=%s' % (k, v) for k, v in context.songs.iteritems()])
return Response(res)
@bfg_view(request_type=IDELETERequest)
def clear_songlist(context, request):
context.songs.clear()
return Response('OK')
@bfg_view(request_type=IGETRequest, name='song')
def view_song(context, request):
song = request.subpath[0]
return Response(str(context.songs[song]))
@bfg_view(request_type=IPOSTRequest, name='song')
def update_song(context, request):
song = request.subpath[0]
context.songs[song] += 1
return Response(str(context.songs[song]))
@bfg_view()
def method_not_allowed(context, request):
return HTTPMethodNotAllowed()
See? Decorators to the rescue. Now you wouldn't even suspect the framework was inspired by Zope, but a lot of cool features and libraries taken from Zope are there in case you feel adventurous one day.
One of the key points made by Eric in his songlist post is that pure WSGI applications are fast. Using Spawning, for example, his simple app gets 1230 requests per second on my 1.86 Mhz Intel Core 2 Duo.
Many of the framework songlist posts make the point that with the framework you get a lot of power while losing around half the speed, which is a good trade-off in my opinion. Here's the repoze.bfg benchmark, also using Spawning:
cguardia@hal9000:~$ ab -n 10000 http://localhost:6540/song/1
This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
...
Finished 10000 requests
Server Software:
Server Hostname: localhost
Server Port: 6540
Document Path: /song/1
Document Length: 1 bytes
Concurrency Level: 1
Time taken for tests: 13.857300 seconds
Complete requests: 10000
Failed requests: 0
Write errors: 0
Total transferred: 1160000 bytes
HTML transferred: 10000 bytes
Requests per second: 721.64 [#/sec] (mean)
Time per request: 1.386 [ms] (mean)
Time per request: 1.386 [ms] (mean, across all concurrent requests)
Transfer rate: 81.69 [Kbytes/sec] received
721 requests per second, not too bad. Incidentally, I used Spawning because that was what the majority of benchmarks used in these posts, but I found the paste http server that repoze.bfg uses as default to be much faster. Using that on my machine, pure WSGI gave me 1780 requests per second and repoze.bfg 1060.
Well, I enjoyed making this post. I like to see different Python web frameworks showing how they do things and learning from each other. It's being said a lot this days, but it really is a great time to be a Python web developer.