wsgistraw
WSGI without
start_response and write
About
wsgistraw is a tiny Python library that simplifies coding WSGI applications and middleware by removing start_response and write from signatures of functions.
We could remove
start_responseand the writer that it implies. This would lead to a signature like:def app(environ): return '200 OK', [('Content-type', 'text/plain')], ['Hello world']That is, return a three-tuple of
(status, headers, app_iter). It’s relatively simple to provide adapters to and from this signature to the WSGI 1.0 signature.
This is what exactly wsgistraw does. It provides several decorators for your simplified functions, classes or factories to bring them to conformity with the WSGI 1.0 spec.
Please send your comments via a comments form below and post your bugs, suggestions, etc. to the wsgistraw’s issue tracker.
Download
wsgistraw is licensed under the LGPL license.
Download the latest release from wsgistraw’s Cheese Shop Page.
Get the source code from the wsgistraw’s Subversion repository.
Changelog
0.1.2, 2007-09-21
A bugfix release. Fixed a bug in app_proxy with extending an iterable with the results of write().
0.1.1, 2007-08-16
Added app_proxy, fixed a couple of inconsistencies with PEP 333.
- (
+) Added publicapp_proxyclass (former_app_proxy, thanks to Ian Bicking) - (
-) If an app useswrite()thenapp_proxyinvokesresponse.close()after iterating over it - (
-) If an app hasn’t invokedstart_resposnse()before returning thenapp_proxyforces this invocation by callingresponse.next()
0.1, 2007-08-11
Initial release.
Examples
This is a “hello world” WSGI application slightly simplified by using wsgistraw:
import wsgistraw
@wsgistraw.app
def hello_world(environ):
response = ["<h1>Hello World!</h1>"]
return "200 OK", [("Content-Type", "text/html")], response
Another app that returns the environ dictionary is:
class environ_debugger(object):
@wsgistraw.app
def __call__(environ):
response = ("%s: %s\n" % (k, v) for k, v in environ.items())
return "200 OK", [("Content-Type", "text/plain")], response
But start_response and write are much more annoying in a WSGI middleware. wsgistraw makes your middleware code cleaner. This is an example of a “lowercase” middleware factory:
@wsgistraw.mid_factory
def lowercase(app):
def mid(environ):
status, headers, response = app(environ)
return status, headers, (s.lower() for s in response)
return mid
The same example coded as a class is:
class _lowercase(object):
def __init__(self, app):
self.app = app
def __call__(self, environ):
status, headers, response = self.app(environ)
return status, headers, (s.lower() for s in response)
lowercase = wsgistraw.mid_factory(_lowercase)
Yet another way of writing such a middleware is using an app_proxy instance:
class lowercase(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
proxy = wsgistraw.app_proxy(self.app)
status, headers, response = proxy(environ)
start_response(status, headers)
return (s.lower() for s in response)
Reference
The wsgistraw library provides the following decorators to allow writing WSGI applications and middleware using the simplified signature mentioned above:
app— decorates WSGI application callables (both functions and methods)mid_factory— decorates WSGI middleware factories, i. e. callables that accept a WSGI application as their first positional parameter and return a middleware callable
Also wsgistraw provides the following classes:
app_proxy— replaces a WSGI application callable on the caller side, returns the application with the simplified signature
Implications
As all calls to write are buffered by a mid_factory decorator instance, a WSGI application that:
- Is called by a (possibly nested) middleware with
mid_factoryand - Uses such
writecalls to transmit its response and - Transmits a very large amount of data or an infinite data stream
will suffer a significant performance penalty and require a huge amount of memory. So don’t use mid_factory in such a setting.
There are no known drawbacks except those mentioned above.
See Also
- Ideas related to WSGI 2.0 at WSGI Wiki
- PEP 333
- WSGI Middleware Considered Harmful (especially the P.P.S.)
8 Comments »
RSS feed for comments on this post. TrackBack URI
Leave a comment
Blog at WordPress.com. | Theme: Pool by Borja Fernandez.
Entries and comments feeds.






I just did something more on the caller side, but to the same effect — if you look at webob.Request.call_application at http://svn.pythonpaste.org/Paste/WebOb/trunk/webob/__init__.py — it’s a very convenient routine to have available.
Comment by Ian Bicking — 2007-08-11 #
@ianbicking: Thanks for your comment. I’ve looked at your code. It has some advantages over mine:
* It has more options to handle exceptions
* It doesn’t replace a WSGI middleware, so the middleware’s attrs are still fully accessible
* It changes nothing in the signature of a middleware
I think I could consider the first two points in the next version of wsgistraw. But my library has some minor advantages too:
* Except wsgistraw decorators, WSGI middleware and applications look like clean pre-WSGI 2.0 callables (see notes below)
* It works for WSGI applications too (though the main point for the library is middleware)
* It is quite minimalistic and follows the philosophy of small tools
Of course I understand, that it’s too early for WSGI 2.0 and that even today’s 2.0 proposals go beyond changes of signatures. And it’s impossible to implement efficient pre-WSGI 2.0 wrappers for every kind of WSGI middleware and applications, because of the
write()-oriented apps with imperative data push.By the way, I believe there is a significant (though easily fixable) problem in your
Request.call_application(). By callinglist.extend()withapp_iteras an argument you iterate over all theapp_iter, so an application that generates a large amount of data and returns it viaapp_iter(not viawrite()) will load all data into memory. I’ve mentioned this point in “Implications” section at the wsgistraw page. My approach is when an application useswrite(), we can do nothing but buffer the generated data; but when it uses an iterator to read data on demand, we can pass it directly to a middleware. See an implementation in the source code of wsgistraw.P. S. AFAIK, you know about Pycoon. I’ve written wsgistraw to simplify coding WSGI middleware and implementing IoC via Java-ish “classloading” of WSGI callables. In Pycoon v0.3 (the first non-alpha release) all the XML pipeline components will be WSGI callables, i. e. WSGI 1.0-compatible apps and mids, Paste-compatible factories and some other forms.
Comment by anrienord — 2007-08-11 #
@ianbicking: I’ve just read your function again and realized that I ignored your
ifcondition:not captured or output. So my comment about unnecessary buffering isn’t relevant any longer.Comment by anrienord — 2007-08-11 #
This looks interesting. However, it means you can’t write the function has a generator. I would suggest changing it to allow:
def app(PATH_INFO, **environ):
yield ‘200 OK’
yield (‘Content-type’, ‘text/plain’)
yield
yield ‘You browsed to %s’ % PATH_INFO
Comment by Calvin Spealman — 2007-08-12 #
@calvinspealman: That’s because of the pre-WSGI 2.0 signature proposal. But in fact you could do something more straightforward. You could define a (maybe nested) generator function and return it as the third tuple parameter, e. g.:
@wsgistraw.appdef app(environ):
def gen():
yield "You're watching %s\n" % environ.get("PATH_INFO")
yield "Something else\n"
return "200 OK", [], gen()
From my POV, this way of writing app functions looks better than yours because it doesn’t mix a status string, headers and response chunks in a single generator, but places them into a more convenient tuple.
Comment by anrienord — 2007-08-13 #
Building a new function every time I handle a request isn’t fun sounding, nor is separating the two.
Comment by Calvin Spealman — 2007-08-14 #
Hi Andrey. Great idea, but your implementation doesn’t appear to be WSGI-compliant.
Specifically, it doesn’t seem to guarantee that a middleware-wrapped application’s ‘close()’ method will be called. Try using the wsgiref.validate middleware on *both* sides of your middleware.
That is, wrap an application with wsgiref.validate, then wrap it with some of your middleware, and then wrap it with wsgiref.validate again.
Comment by Phillip J. Eby — 2007-09-04 #
@pje: Hi Phillip. Thanks for your comment! I’ve run this validity check with several wsgistraw-decorated middleware callables and found one bug with extending an iterable with the results of
write()(will be fixed in 0.1.2), but all other stuff seems to be WSGI-compliant (as I believe and aswsgiref.validatehas shown). In fact the usage ofclose()was WSGI-incompatible in wsgistraw 0.1. But I fixed this issue in 0.1.1, 2007-08-16.If I’ve got it right, you mean that a middleware must call
close()method of the application if it returns its own iterator, not that one returned by the application. In all other casesclose()must be called by a WSGI server, not by a middleware.Comment by anrienord — 2007-09-05 #