diff options
| -rw-r--r-- | docs/api/renderers.rst | 2 | ||||
| -rw-r--r-- | docs/narr/renderers.rst | 10 | ||||
| -rw-r--r-- | pyramid/renderers.py | 80 | ||||
| -rw-r--r-- | pyramid/tests/test_renderers.py | 29 |
4 files changed, 94 insertions, 27 deletions
diff --git a/docs/api/renderers.rst b/docs/api/renderers.rst index 312aa0b31..ea000ad02 100644 --- a/docs/api/renderers.rst +++ b/docs/api/renderers.rst @@ -11,6 +11,8 @@ .. autofunction:: render_to_response +.. autoclass:: JSON + .. autoclass:: JSONP .. attribute:: null_renderer diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 76035cbdf..47182c09e 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -177,8 +177,8 @@ using the API of the ``request.response`` attribute. See .. index:: pair: renderer; JSON -``json``: JSON Renderer -~~~~~~~~~~~~~~~~~~~~~~~ +JSON Renderer +~~~~~~~~~~~~~ The ``json`` renderer renders view callable results to :term:`JSON`. It passes the return value through the ``json.dumps`` standard library function, @@ -207,7 +207,10 @@ representing the JSON serialization of the return value: '{"content": "Hello!"}' The return value needn't be a dictionary, but the return value must contain -values serializable by :func:`json.dumps`. +values serializable by :func:`json.dumps`. Extra arguments can be passed +to :func:`json.dumps` by overriding the default renderer. See +:class:`pyramid.renderers.JSON` and +:ref:`_adding_and_overriding_renderers` for more information. You can configure a view to use the JSON renderer by naming ``json`` as the ``renderer`` argument of a view configuration, e.g. by using @@ -221,7 +224,6 @@ You can configure a view to use the JSON renderer by naming ``json`` as the context='myproject.resources.Hello', renderer='json') - Views which use the JSON renderer can vary non-body response attributes by using the api of the ``request.response`` attribute. See :ref:`request_response_attr`. diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 14941c61a..401d1fdd4 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -144,17 +144,6 @@ def get_renderer(renderer_name, package=None): # concrete renderer factory implementations (also API) -def json_renderer_factory(info): - def _render(value, system): - request = system.get('request') - if request is not None: - response = request.response - ct = response.content_type - if ct == response.default_content_type: - response.content_type = 'application/json' - return json.dumps(value) - return _render - def string_renderer_factory(info): def _render(value, system): if not isinstance(value, string_types): @@ -168,7 +157,67 @@ def string_renderer_factory(info): return value return _render -class JSONP(object): +class JSON(object): + """ Renderer that returns a JSON-encoded string. + + Configure a custom JSON renderer using the + :meth:`pyramid.config.Configurator.add_renderer` API at application + startup time: + + .. code-block:: python + + from pyramid.config import Configurator + + config = Configurator() + config.add_renderer('myjson', JSON(indent=4, cls=MyJSONEncoder)) + + Once this renderer is registered via + :meth:`~pyramid.config.Configurator.add_renderer` as above, you can use + ``myjson`` as the ``renderer=`` parameter to ``@view_config`` or + :meth:`pyramid.config.Configurator.add_view``: + + .. code-block:: python + + from pyramid.view import view_config + + @view_config(renderer='myjson') + def myview(request): + return {'greeting':'Hello world'} + + .. note:: This feature is new in Pyramid 1.3. Prior to 1.3 there was + no public API for supplying options to the underlying + :func:`json.dumps` without defining a custom renderer. + """ + + def __init__(self, **kw): + """ Any keyword arguments will be forwarded to + :func:`json.dumps`. + """ + self.kw = kw + + def __call__(self, info): + """ Returns a plain JSON-encoded string with content-type + ``application/json``. The content-type may be overridden by + setting ``request.response.content_type``.""" + def _render(value, system): + request = system.get('request') + if request is not None: + response = request.response + ct = response.content_type + if ct == response.default_content_type: + response.content_type = 'application/json' + return self.value_to_json(value) + return _render + + def value_to_json(self, value): + """ Convert a Python object to a JSON string. + + By default, this uses the :func:`json.dumps` from the stdlib.""" + return json.dumps(value, **self.kw) + +json_renderer_factory = JSON() # bw compat + +class JSONP(JSON): """ `JSONP <http://en.wikipedia.org/wiki/JSONP>`_ renderer factory helper which implements a hybrid json/jsonp renderer. JSONP is useful for making cross-domain AJAX requests. @@ -210,9 +259,10 @@ class JSONP(object): See also: :ref:`jsonp_renderer`. """ - - def __init__(self, param_name='callback'): + + def __init__(self, param_name='callback', **kw): self.param_name = param_name + JSON.__init__(self, **kw) def __call__(self, info): """ Returns JSONP-encoded string with content-type @@ -221,7 +271,7 @@ class JSONP(object): plain-JSON encoded string with content-type ``application/json``""" def _render(value, system): request = system['request'] - val = json.dumps(value) + val = self.value_to_json(value) callback = request.GET.get(self.param_name) if callback is None: ct = 'application/json' diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index b32e68e25..c2450b875 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -340,35 +340,48 @@ class TestChameleonRendererLookup(unittest.TestCase): self.assertNotEqual(reg.queryUtility(ITemplateRenderer, name=spec), None) -class Test_json_renderer_factory(unittest.TestCase): +class TestJSON(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() - - def _callFUT(self, name): - from pyramid.renderers import json_renderer_factory - return json_renderer_factory(name) + + def _makeOne(self, **kw): + from pyramid.renderers import JSON + return JSON(**kw) def test_it(self): - renderer = self._callFUT(None) + renderer = self._makeOne()(None) result = renderer({'a':1}, {}) self.assertEqual(result, '{"a": 1}') def test_with_request_content_type_notset(self): request = testing.DummyRequest() - renderer = self._callFUT(None) + renderer = self._makeOne()(None) renderer({'a':1}, {'request':request}) self.assertEqual(request.response.content_type, 'application/json') def test_with_request_content_type_set(self): request = testing.DummyRequest() request.response.content_type = 'text/mishmash' - renderer = self._callFUT(None) + renderer = self._makeOne()(None) renderer({'a':1}, {'request':request}) self.assertEqual(request.response.content_type, 'text/mishmash') + def test_with_custom_encoder(self): + from datetime import datetime + from json import JSONEncoder + class MyEncoder(JSONEncoder): + def default(self, obj): + if isinstance(obj, datetime): + return obj.isoformat() + return super(JSONEncoder, self).default(obj) + now = datetime.utcnow() + renderer = self._makeOne(cls=MyEncoder)(None) + result = renderer({'a':now}, {}) + self.assertEqual(result, '{"a": "%s"}' % now.isoformat()) + class Test_string_renderer_factory(unittest.TestCase): def _callFUT(self, name): from pyramid.renderers import string_renderer_factory |
