Source code for weblayer.normalise

#!/usr/bin/env python
# -*- coding: utf-8 -*-

""" :py:mod:`weblayer.normalise` provides 
  :py:class:`DefaultToJSONResponseNormaliser`, an implementation of
  :py:class:`~weblayer.interfaces.IResponseNormaliser`.
  
  :py:class:`DefaultToJSONResponseNormaliser` adapts a response object::
  
      >>> from mock import Mock
      >>> response = Mock()
      >>> normaliser = DefaultToJSONResponseNormaliser(response)
      >>> normaliser.response == response
      True
  
  Provides a ``normalise()`` method that takes a single argument and uses it to
  update and or replace the original response object before returning it::
  
      >>> class MockResponse(Mock):
      ...     implements(IResponse)
      ... 
      >>> mock_response = MockResponse()
      >>> r = normaliser.normalise(mock_response)
      >>> r == mock_response
      True
      >>> r = normaliser.normalise('a')
      >>> r.body == 'a'
      True
      >>> r = normaliser.normalise(u'a')
      >>> r.unicode_body == u'a'
      True
      >>> r = normaliser.normalise(None)
      >>> r == normaliser.response
      True
  
  If the argument provided isn't ``callable()``, a ``basestring`` or ``None``,
  the default implementation tries to `JSON`_ encode it::
  
      >>> r = normaliser.normalise({'a': u'b'})
      >>> r.content_type
      'application/json; charset=UTF-8'
      >>> r.unicode_body
      u'{"a": "b"}'
  
  .. _`json`: http://www.json.org/
"""

__all__ = [
    'DefaultToJSONResponseNormaliser'
]

from zope.component import adapts
from zope.interface import implements

from interfaces import IResponse, IResponseNormaliser
from utils import json_encode as utils_json_encode

[docs]class DefaultToJSONResponseNormaliser(object): """ Adapter to normalise a response. """ adapts(IResponse) implements(IResponseNormaliser) def __init__( self, response, json_encode=None, json_content_type='application/json; charset=UTF-8' ): """ Initialise a `DefaultToJSONResponseNormaliser`:: >>> response = object() >>> json_content_type = 'a' >>> json_encode = object() >>> normaliser = DefaultToJSONResponseNormaliser( ... response, ... json_content_type=json_content_type, ... json_encode=json_encode ... ) ``response`` is available as ``self.response``:: >>> normaliser.response == response True ``json_encode`` is available as ``self._json_encode``:: >>> normaliser._json_encode == json_encode True ``json_content_type`` is available as ``self._json_content_type``:: >>> normaliser._json_content_type == json_content_type True which defaults to ``'application/json; charset=UTF-8'``:: >>> default = 'application/json; charset=UTF-8' >>> normaliser = DefaultToJSONResponseNormaliser( ... response, ... json_encode=json_encode ... ) >>> normaliser._json_content_type == default True """ self.response = response if json_encode is None: self._json_encode = utils_json_encode else: self._json_encode = json_encode self._json_content_type = json_content_type
[docs] def normalise(self, handler_response): """ Update and return self.response appropriately. >>> from mock import Mock >>> response = Mock() >>> normaliser = DefaultToJSONResponseNormaliser( ... response, ... json_encode=None ... ) If ``handler_response`` is ``callable()`` then just use that. The intention being that the ``callable()`` is a WSGI application:: >>> def app(environ, start_response): ... headers = [('Content-type', 'text/plain')] ... start_response('200 OK', headers) ... return [''] ... >>> r = normaliser.normalise(app) >>> r == app True But, any old ``callable()`` will sneak through:: >>> def foo(): ... pass ... >>> r = normaliser.normalise(foo) >>> r == foo True Otherwise if it's a ``str`` or a ``unicode`` use that as the response body:: >>> r = normaliser.normalise('a') >>> r.body == 'a' True >>> r = normaliser.normalise(u'a') >>> r.unicode_body == u'a' True If it's ``None`` then just return the origin ``response``:: >>> normaliser = DefaultToJSONResponseNormaliser( ... 42, ... json_encode=None ... ) >>> normaliser.normalise(None) 42 Otherwise (with this particular implementation) assume we want to encode ``handler_response`` as a JSON string as use that as the response body:: >>> response = Mock() >>> json_encode = Mock() >>> normaliser = DefaultToJSONResponseNormaliser( ... response, ... json_encode=json_encode ... ) >>> json_encode.return_value = '{"a": "b"}' >>> r = normaliser.normalise({'a': 'b'}) >>> r.content_type == normaliser._json_content_type True >>> json_encode.call_args[0][0] == {'a': 'b'} True >>> r.body '{"a": "b"}' >>> json_encode.return_value = u'{"a": "b"}' >>> r = normaliser.normalise({'a': u'b'}) >>> r.unicode_body u'{"a": "b"}' """ if callable(handler_response): self.response = handler_response elif isinstance(handler_response, str): self.response.body = handler_response elif isinstance(handler_response, unicode): self.response.unicode_body = handler_response elif handler_response is None: # leave self.response alone pass else: # assume it's json data self.response.content_type = self._json_content_type json_string = self._json_encode(handler_response) if isinstance(json_string, str): self.response.body = json_string else: # isinstance(json_string, unicode): self.response.unicode_body = json_string return self.response