Minority Opinions

Not everyone can be mainstream, after all.

Interfacing with Self

leave a comment »

Graham Dumpleton noticed a problem with simple WSGI middleware, and provided a couple of templates for fixing it.  He wasn’t particularly happy with either; one subtly defied the WSGI specification, and the other split state tracking across two classes.  I noticed a way to repair those issues, but my comment seems to have been swallowed; perhaps the comment system didn’t like the non-breaking spaces I used to ram something that looks like Python through the limited format options available, or maybe my typos caused it to be rejected by the author.  Fortunately, I had saved my code on Pastebin, so I can share it with you:

class ClosingMiddleware(object):
    def __init__(self, application):
        self.application = application
        self.iterable = None

    def __call__(self, environ, start_response):
        self.iterable = self.application(environ, start_response)
        return self

    def __iter__(self):
        for data in self.iterable:
            yield data

    def close(self):
        if hasattr(self.iterable, 'close'):
            self.iterable.close()
 

The key here is to notice that interface required of __call__‘s result doesn’t conflict with the interface required of the middleware itself, so they can happily co-exist in the same object.  The explicit close method isn’t actually necessary, but might provide a place to hook your own functionality; if you don’t need it, feel free to replace it with self.close = self.iterable.close within the __call__ method.

Sadly, that’s not the last word in middleware templates.  Two posts later, another potential pain point is introduced, with the return value of start_response.  Combining the ideas in that post with those above, we find an even more complex template, which is almost certainly not the last word on the topic:

class Middleware(object):
    def __init__(self, application):
        self.application = application
        self.iterable = None

    def transform(self, data):
        # Operate on data here, as necessary.
        return data

    def __call__(self, environ, start_response):
        def _start_response(status, response_headers, *args):
            write = start_response(status, response_headers, *args)
            def _write(data):
                write(self.transform(data))
            return _write

        self.iterable = self.application(environ, _start_response)
        if hasattr(self.iterable, 'close'):
            self.close = self.iterable.close
        return self

    def __iter__(self):
        for data in self.iterable:
            yield self.transform(data)
 

I considered moving _start_response and _write to separate functions, but decided they weren’t worth the extra instance variables; profiling could prove me wrong on that, though, given that closures can be memory-intensive.  It might also be possible to optimize for the case where the iterable doesn’t have a close method, but probably not worth the extra code.

Now, can anyone explain to me why WSGI needs duplicate data paths like this?  Couldn’t some sort of middleware have converted one to the other?  Should I even care?

Advertisements

Written by eswald

16 Oct 2012 at 6:00 pm

Posted in Python, Technology

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