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_response and 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.

WSGI 2.0 page at WSGI Wiki

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 public app_proxy class (former _app_proxy, thanks to Ian Bicking)
  • (-) If an app uses write() then app_proxy invokes response.close() after iterating over it
  • (-) If an app hasn’t invoked start_resposnse() before returning then app_proxy forces this invocation by calling response.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_factory and
  • Uses such write calls 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

8 Comments »

RSS feed for comments on this post. TrackBack URI

  1. 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.

  2. @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 calling list.extend() with app_iter as an argument you iterate over all the app_iter, so an application that generates a large amount of data and returns it via app_iter (not via write()) will load all data into memory. I’ve mentioned this point in “Implications” section at the wsgistraw page. My approach is when an application uses write(), 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.

  3. @ianbicking: I’ve just read your function again and realized that I ignored your if condition: not captured or output. So my comment about unnecessary buffering isn’t relevant any longer.

  4. 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

  5. @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.app
    def 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.

  6. Building a new function every time I handle a request isn’t fun sounding, nor is separating the two.

  7. 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.

  8. @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 as wsgiref.validate has shown). In fact the usage of close() 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 cases close() must be called by a WSGI server, not by a middleware.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.
Entries and comments feeds.

%d bloggers like this: