Documentation for pulsar 0.9.2. For development docs, go here.

Source code for pulsar.apps.wsgi.handlers

'''
This section describes the asynchronous WSGI specification used by pulsar.
It is a superset of the `WSGI 1.0.1`_ specification for synchronous
server/middleware.
If an application handler is synchronous, this specification is exactly
equivalent to `WSGI 1.0.1`_. Changes with respect `WSGI 1.0.1`_
concern asynchronous responses and nothing else.

Introduction
========================

The WSGI interface has two sides: the ``server`` or ``gateway`` side, and the
``application`` or ``framework`` side. The server side invokes a callable
object, here referred as **application handler**, that is provided by the
application side.


.. note::

    A standard WSGI application handler is always a callable, either a function
    or a callable object, which accepts two positional arguments:
    ``environ`` and ``start_response``. When called by the server,
    the application object must return an iterable yielding zero or more bytes.


.. _wsgi-handlers:

Application handlers
=============================

An asynchronous :ref:`application handler <wsgi-handlers>` must conform
with the standard `WSGI 1.0.1`_ specification with the following two
exceptions:

* It can return a :class:`~asyncio.Future`.
* If it returns a :class:`~asyncio.Future`, it must results in an
  :ref:`asynchronous iterable <wsgi-async-iter>`.

Pulsar is shipped with two WSGI application handlers documented below.

.. _wsgi-async-iter:

Asynchronous Iterable
========================

An asynchronous iterable is an iterable over a combination of ``bytes`` or
:class:`~asyncio.Future` which result in ``bytes``.
For example this could be an asynchronous iterable::

    def simple_async():
        yield b'hello'
        c = Future()
        c.set_result(b' ')
        yield c
        yield b'World!'


.. _wsgi-lazy-handler:

WsgiHandler
======================

The first application handler is the :class:`WsgiHandler`
which is a step above the :ref:`hello callable <tutorials-hello-world>`
in the tutorial. It accepts two iterables, a list of
:ref:`wsgi middleware <wsgi-middleware>` and an optional list of
:ref:`response middleware <wsgi-response-middleware>`.

.. autoclass:: WsgiHandler
   :members:
   :member-order: bysource


.. _wsgi-handler:

Lazy Wsgi Handler
======================

.. autoclass:: LazyWsgi
   :members:
   :member-order: bysource


.. _wsgi-pulsar-variables:

Pulsar Variables
======================
Pulsar injects two server-defined variables into the WSGI environ:

* ``pulsar.connection``, the :class:`.Connection` serving the request
* ``pulsar.cfg``, the :class:`.Config` dictionary of the server

The event loop serving the application can be retrieved from the connection
via the ``_loop`` attribute::

    loop = environ['pulsar.connection']._loop

.. _WSGI: http://www.wsgi.org
.. _`WSGI 1.0.1`: http://www.python.org/dev/peps/pep-3333/
'''
from pulsar import (From, Http404, coroutine_return, isfuture,
                    get_event_loop, task, async)
from pulsar.utils.log import LocalMixin, local_method

from .utils import handle_wsgi_error
from .wrappers import WsgiResponse


__all__ = ['WsgiHandler', 'LazyWsgi']


[docs]class WsgiHandler(object): '''An handler for application conforming to python WSGI_. .. attribute:: middleware List of :ref:`asynchronous WSGI middleware <wsgi-middleware>` callables which accept ``environ`` and ``start_response`` as arguments. The order matter, since the response returned by the callable is the non ``None`` value returned by a middleware. .. attribute:: response_middleware List of functions of the form:: def ..(environ, response): ... where ``response`` is a :ref:`WsgiResponse <wsgi-response>`. Pulsar contains some :ref:`response middlewares <wsgi-response-middleware>`. ''' def __init__(self, middleware=None, response_middleware=None, **kwargs): if middleware: middleware = list(middleware) self.middleware = middleware or [] self._response_done = False if response_middleware is False: self._response_done = True self.response_middleware = response_middleware or [] def __call__(self, environ, start_response): '''The WSGI callable''' response = AsyncResponse(environ, start_response, iter(self.middleware), iter(self.response_middleware), self._response_done) return response()
class AsyncResponse(object): __slots__ = ('environ', 'start_response', 'middleware', 'response_middleware', '_response_done') def __init__(self, environ, start_response, middleware, response_middleware, response_done): self.environ = environ self.start_response = start_response self.middleware = middleware self.response_middleware = response_middleware self._response_done = response_done def __call__(self, resp=None, exc=None): try: while not exc and resp is None: try: handler = next(self.middleware) except StopIteration: break else: resp = handler(self.environ, self.start_response) if resp is not None: try: resp = async(resp) except TypeError: pass else: return self._async(self, resp, True) if not exc and resp is None: if not self._response_done: raise Http404 except Exception as exc: if self._response_done: raise resp = handle_wsgi_error(self.environ, exc) else: if exc: resp = handle_wsgi_error(self.environ, exc) # if not self._response_done: self._response_done = True return self._response(resp) return resp def _response(self, resp=None, exc=None): while not exc: try: handler = next(self.response_middleware) resp = handler(self.environ, resp) try: return self._async(self._response, async(resp)) except TypeError: pass except StopIteration: break if isinstance(resp, WsgiResponse): self.start_response(resp.status, resp.get_headers()) return resp @task def _async(self, callable, future, safe=False): while isfuture(future): kw = {} try: resp = yield future except Exception as exc: if not safe: raise future = callable(exc=exc) else: future = callable(resp) coroutine_return(future)
[docs]class LazyWsgi(LocalMixin): '''A :ref:`wsgi handler <wsgi-handlers>` which loads the actual handler the first time it is called. Subclasses must implement the :meth:`setup` method. Useful when working in multiprocessing mode when the application handler must be a ``picklable`` instance. This handler can rebuild its wsgi :attr:`handler` every time is pickled and un-pickled without causing serialisation issues. ''' def __call__(self, environ, start_response): return self.handler(environ)(environ, start_response) @local_method def handler(self, environ=None): '''The :ref:`wsgi application handler <wsgi-handlers>` which is loaded via the :meth:`setup` method, once only, when first accessed. ''' return self.setup(environ)
[docs] def setup(self, environ=None): '''The setup function for this :class:`LazyWsgi`. Called once only the first time this application handler is invoked. This **must** be implemented by subclasses and **must** return a :ref:`wsgi application handler <wsgi-handlers>`. ''' raise NotImplementedError