"""A WSGI middleware that run the application in a green thread. THIS SOFTWARE IS UNDER MIT LICENSE. Copyright (c) 2010 Manlio Perillo (manlio.perillo@gmail.com) """ import sys from greenlet import getcurrent, greenlet from mako.template import Template class GState(tuple): pass GCONTINUE = 0 GSUSPEND = 1 class middleware(object): """A middleware that use greenlet to provide non linear control flow. """ def __init__(self, application): self.app = application def __call__(self, environ, start_response): def _start_response(status, headers): start_response(status, headers) return self.write # Initialize greenlet and state gp = greenlet(self.app) self.in_app_iter = False r = gp.switch(environ, _start_response) while isinstance(r, GState): buf, status = r if status == GSUSPEND: # TODO: async WSGI extension # environ['wsgiorg.suspend']() # TODO: store environ as a per green thread state # gp.environ = environ pass yield buf r = gp.switch() # The application returned the app_iter object self.in_app_iter = True for buf in r: yield buf def write(self, buf): """WSGI "safe" write callable. """ gp = getcurrent() if self.in_app_iter: exc = ValueError( 'write callable called from application iterator') gp.throw(exc) else: gp.parent.switch(GState((buf, GCONTINUE))) def flush(ctx): """A Mako function that flush the current buffer and return data to the caller thread. """ gp = getcurrent() body = ctx._buffer_stack[0].getvalue() # TODO: get encoding to use from WSGI environ, stored in green # thread instance buf = body.encode('us-ascii') # XXX assume FastEncodingBuffer is used ctx._buffer_stack[0].data[:] = [] gp.parent.switch(GState((buf, GCONTINUE))) return '' @middleware def application(environ, start_response): """A sample WSGI application that use Mako. """ headers = [('Content-Type', 'text/html')] write = start_response('200 OK', headers) write('some data from write callable\n') buf = tmpl.render_unicode(body='Body') return [buf] tmpl = Template(""" <%namespace name="greenlet" module="__main__" /> Mako example
Header
${greenlet.flush()} ## TODO: async subrequest
${body}
${greenlet.flush()}
Footer
""") if __name__ == '__main__': def start_response(status, headers): def write(buf): raise NotImplementedError print 'status:', status print 'headers', headers return write environ = {} r = application(environ, start_response) for i, buf in enumerate(r): print 'chunk #%d' % i sys.stdout.write(buf)