Lately I’ve been writing a few applications (e.g., PickyWiki and a revisiting a request-tracking application VaingloriousEye), and I usually use no framework at all. Pylons would be a natural choice, but given that I am comfortable with all the components, I find myself inclined to assemble the pieces myself.
In the process I keep writing bits of code to make WSGI applications from simple WebOb -based request/response cycles. The simplest form looks like this:
from webob import Request, Response, exc
def wsgiwrap(func):
def wsgi_app(environ, start_response):
req = Request(environ)
try:
resp = func(req)
except exc.HTTPException, e:
resp = e
return resp(environ, start_response)
return wsgi_app
@wsgiwrap
def hello_world(req):
return Response('Hi %s!' % (req.POST.get('name', 'You')))
But each time I’d write it, I change things slightly, implementing more or less features. For instance, handling methods, or coercing other responses, or handling middleware.
Having implemented several of these (and reading other people’s implementations) I decided I wanted WebOb to include a kind of reference implementation. But I don’t like to include anything in WebOb unless I’m sure I can get it right, so I’d really like feedback. (There’s been some less than positive feedback, but I trudge on.)
My implementation is in a WebOb branch, primarily in webob.dec (along with some doctests).
The most prominent way this is different from the example I gave is that it doesn’t change the function signature, instead it adds an attribute .wsgi_app which is WSGI application associated with the function. My goal with this is that the decorator isn’t intrusive. Here’s the case where I’ve been bothered:
class MyClass(object):
@wsgiwrap
def form(self, req):
return Response(form_html...)
@wsgiwrap
def form_post(self, req):
handle submission
OK, that’s fine, then I add validation:
@wsgiwrap
def form_post(self, req):
if req not valid:
return self.form
handle submission
This still works, because the decorator allows you to return any WSGI application, not just a WebOb Response object. But that’s not helpful, because I need errors…
@wsgiwrap
def form_post(self, req):
if req not valid:
return self.form(req, errors)
handle submission
That is, I want to have an option argument to the form method that passes in errors. But I can’t do this with the traditional wsgiwrap decorator, instead I have to refactor the code to have a third method that both form and form_post use. Of course, there’s more than one way to address this issue, but this is the technique I like.
The one other notable feature is that you can also make middleware:
@wsgify.middleware
def cap_middleware(req, app):
resp = app(req)
resp.body = resp.body.upper()
return resp
capped_app = cap_middleware(some_wsgi_app)
Otherwise, for some reason I’ve found myself putting an inordinate amount of time into __repr__. Why I’ve done this I cannot say.
Automatically generated list of related posts:
- What Does A WebOb App Look Like? Lately I’ve been writing code using WebOb and just a...
- WebOb I’ve have it in my head to extract/rewrite parts of...
- JSON-RPC WebOb Example I just saw this json-rpc recipe go by as a...
- WebOb Do-It-Yourself Framework My old do-it-yourself framework tutorial was getting a bit long...
- Decorators and Descriptors So, decorators are neat (maybe check out a new tutorial...
Having a third method is not a problem, I think. I suppose wsgify code would look like this:
With third method it’s almost the same:
Overriding gen_form wouldn’t automatically override .form though.
Are there more cases to justify extra parameter passing to wrapped methods? For this one having a separate method looks like the correct solution.
(BTW, “Markdown formatting” link is broken)
Hi Ian.
Just quickly had a look at the implementation and it seems overly complex to me. I’ve had exception handling in an early version of the “Request.application” decorator in Werkzeug as well and it turned out to be a mistake. It’s easier to write a simple try/except than configuring a decorator to handle some exceptions (like 404 not found).
Right now the full implementation is a two liner and works: [Request.application](http://dev.pocoo.org/projects/werkzeug/browser/werkzeug/wrappers.py?rev=788%3A901b6c157194#L162)
I think you’re slightly over-engineering your decorator here.
Why was exception handling a mistake? I’ve never found the
HTTP*
exceptions to be particularly problematic (at least on this scale — raising them across WSGI boundaries is bad).I’ve added another feature,
wsgify.instantiate
which can be used to create controller-like classes, something like:Then
MyClass.wsgi_app
is a WSGI application that will instantiate the class for every call. You could then create a web.py-style class like this:Or, one could make the class object itself a WSGI app via baseclass:
This can’t get any more concise and this wasn’t even a design consideration for webob_wrap.
[Sorry for stalking your webob.dec effort, I hope this feedback is not entirely unwanted.]
Metaclasses just scare the heck out of me for something like this.
Another detail that has made some things harder is that I like custom Request/Response subclasses, and I want the decorator to be at least somewhat orthogonal to those classes, so you have to have some way to add parameters to these things.
One thing that I may have over-thought is whether something like
.wsgi_app
should just be a very straight class method for this case.I wonder how do you customize the Request subclasses? I mean what do you put there that wouldn’t fit in webob itself — I can’t remember if I ever needed something like that.
Well, here’s an example of a few minor things: http://bitbucket.org/ianb/pickywiki/src/tip/pickywiki/reqobj.py
There’s several class variables on Request and Response that are intended for customization, and any WSGI extension could potentially be represented with a method on the request.
Also you could [pretend you are Django](http://svn.pythonpaste.org/Paste/WebOb/branches/ianb-decorator-experiment/webob/django.py)
It occurs to me with WSGI 2 that it will be relatively easy to stuff both WSGI and the request/response calling convention into
__call__
. And maybe it’s worth doing that even for WSGI 1… it’s just a simple type check, really, and an optionalstart_response
argument in WSGI 1 (gone from WSGI 2)i think is right,instead it adds an attribute .wsgi_app which is WSGI application associated with the function,Are there more cases to justify extra parameter passing to wrapped methods? For this one having a separate method looks like the correct solution.