diff options
| author | Chris McDonough <chrism@plope.com> | 2012-04-17 04:59:48 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2012-04-17 04:59:48 -0400 |
| commit | c8aab32b60706f700b7f6a70d967727c353e3d54 (patch) | |
| tree | c7db8d4401e8a0dc8dc023679eb826d47b958f15 | |
| parent | 04e71690aaa32cb75af1095654d3f7e89946dab3 (diff) | |
| parent | 18410a6d9d64786f272268db6368981955ff9f10 (diff) | |
| download | pyramid-c8aab32b60706f700b7f6a70d967727c353e3d54.tar.gz pyramid-c8aab32b60706f700b7f6a70d967727c353e3d54.tar.bz2 pyramid-c8aab32b60706f700b7f6a70d967727c353e3d54.zip | |
Merge branch 'mmerickel-feature.json-api'
| -rw-r--r-- | CONTRIBUTORS.txt | 2 | ||||
| -rw-r--r-- | docs/api/renderers.rst | 2 | ||||
| -rw-r--r-- | docs/narr/renderers.rst | 48 | ||||
| -rw-r--r-- | pyramid/renderers.py | 84 | ||||
| -rw-r--r-- | pyramid/tests/test_renderers.py | 8 |
5 files changed, 74 insertions, 70 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 365e3e455..4b780d3a7 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -127,7 +127,7 @@ Contributors - Wichert Akkerman, 2011/01/19 -- Christopher Lambacehr, 2011/02/12 +- Christopher Lambacher, 2011/02/12 - Malthe Borch, 2011/02/28 diff --git a/docs/api/renderers.rst b/docs/api/renderers.rst index ab182365e..ea000ad02 100644 --- a/docs/api/renderers.rst +++ b/docs/api/renderers.rst @@ -15,8 +15,6 @@ .. autoclass:: JSONP -.. autoclass:: ObjectJSONEncoder - .. attribute:: null_renderer An object that can be used in advanced integration cases as input to the diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 34bee3c7f..02063a112 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -177,6 +177,8 @@ using the API of the ``request.response`` attribute. See .. index:: pair: renderer; JSON +.. _json_renderer: + JSON Renderer ~~~~~~~~~~~~~ @@ -223,9 +225,9 @@ You can configure a view to use the JSON renderer by naming ``json`` as the :linenos: config.add_view('myproject.views.hello_world', - name='hello', - context='myproject.resources.Hello', - renderer='json') + name='hello', + 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 @@ -260,20 +262,38 @@ strings, and so forth). # the JSON value returned by ``objects`` will be: # [{"x": 1}, {"x": 2}] -.. note:: +If you aren't the author of the objects being serialized, it won't be +possible (or at least not reasonable) to add a custom ``__json__`` method to +to their classes in order to influence serialization. If the object passed +to the renderer is not a serializable type, and has no ``__json__`` method, +usually a :exc:`TypeError` will be raised during serialization. You can +change this behavior by creating a JSON renderer with a "default" function +which tries to "sniff" at the object, and returns a valid serialization (a +string) or raises a TypeError if it can't determine what to do with the +object. A short example follows: + +.. code-block:: python + :linenos: - Honoring the ``__json__`` method of custom objects is a feature new in - Pyramid 1.4. + from pyramid.renderers import JSON -.. warning:: + def default(obj): + if isinstance(obj, datetime.datetime): + return obj.isoformat() + raise TypeError('%r is not serializable % (obj,)) + + json_renderer = JSON(default=default) + + # then during configuration .... + config = Configurator() + config.add_renderer('json', json_renderer) + +See :class:`pyramid.renderers.JSON` and +:ref:`adding_and_overriding_renderers` for more information. + +.. note:: - The machinery which performs the ``__json__`` method-calling magic is in - the :class:`pyramid.renderers.ObjectJSONEncoder` class. This class will - be used for encoding any non-basic Python object when you use the default - ```json`` or ``jsonp`` renderers. But if you later define your own custom - JSON renderer and pass it a "cls" argument signifying a different encoder, - the encoder you pass will override Pyramid's use of - :class:`pyramid.renderers.ObjectJSONEncoder`. + Serializing custom objects is a feature new in Pyramid 1.4. .. index:: pair: renderer; JSONP diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 0adadf726..b393a40a6 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -157,40 +157,11 @@ def string_renderer_factory(info): return value return _render -class ObjectJSONEncoder(json.JSONEncoder): - """ The default JSON object encoder (a subclass of json.Encoder) used by - :class:`pyramid.renderers.JSON` and :class:`pyramid.renderers.JSONP`. It - is used when an object returned from a view and presented to a JSON-based - renderer is not a builtin Python type otherwise serializable to JSON. - - This ``json.Encoder`` subclass overrides the ``json.Encoder.default`` - method. The overridden method looks for a ``__json__`` attribute on the - object it is passed. If it's found, the encoder will assume it's - callable, and will call it with no arguments to obtain a value. The - overridden ``default`` method will then return that value (which must be - a JSON-serializable basic Python type). - - If the object passed to the overridden ``default`` method has no - ``__json__`` attribute, the ``json.JSONEncoder.default`` method is called - with the object that it was passed (which will end up raising a - :exc:`TypeError`, as it would with any other unserializable type). - - This class will be used only when you set a JSON or JSONP - renderer and you do not define your own custom encoder class. - - .. note:: This feature is new in Pyramid 1.4. - """ - - def default(self, obj): - if hasattr(obj, '__json__'): - return obj.__json__() - return json.JSONEncoder.default(self, obj) - 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 + :meth:`~pyramid.config.Configurator.add_renderer` API at application startup time: .. code-block:: python @@ -198,12 +169,11 @@ class JSON(object): from pyramid.config import Configurator config = Configurator() - config.add_renderer('myjson', JSON(indent=4, cls=MyJSONEncoder)) + config.add_renderer('myjson', JSON(indent=4)) - Once this renderer is registered via - :meth:`~pyramid.config.Configurator.add_renderer` as above, you can use + Once this renderer is registered as above, you can use ``myjson`` as the ``renderer=`` parameter to ``@view_config`` or - :meth:`pyramid.config.Configurator.add_view``: + :meth:`~pyramid.config.Configurator.add_view``: .. code-block:: python @@ -219,12 +189,20 @@ class JSON(object): no public API for supplying options to the underlying :func:`json.dumps` without defining a custom renderer. + You can pass a ``default`` argument to this class' constructor (which + should be a function) to customize what happens when it attempts to + serialize types unrecognized by the base ``json`` module. See + :ref:`json_serializing_custom_objects` for more information. """ def __init__(self, **kw): - """ Any keyword arguments will be forwarded to - :func:`json.dumps`. - """ + """ Any keyword arguments will be passed to the ``json.dumps`` + function. A notable exception is the keyword argument ``default``, + which is wrapped in a function that sniffs for ``__json__`` + attributes before it is passed along to ``json.dumps``""" + # we wrap the default callback with our own to get __json__ attr + # sniffing + self._default = kw.pop('default', None) self.kw = kw def __call__(self, info): @@ -238,23 +216,29 @@ class JSON(object): ct = response.content_type if ct == response.default_content_type: response.content_type = 'application/json' - return self.value_to_json(value) + return self._dumps(value) return _render - def value_to_json(self, value): - """ Convert a Python object to a JSON string. + def _default_encode(self, obj): + if hasattr(obj, '__json__'): + return obj.__json__() + + if self._default is not None: + return self._default(obj) + raise TypeError('%r is not JSON serializable' % (obj,)) + + def _dumps(self, obj): + """ Encode a Python object to a JSON string. By default, this uses the :func:`json.dumps` from the stdlib.""" - if not self.kw.get('cls'): - self.kw['cls'] = ObjectJSONEncoder - return json.dumps(value, **self.kw) + return json.dumps(obj, default=self._default_encode, **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. + making cross-domain AJAX requests. Configure a JSONP renderer using the :meth:`pyramid.config.Configurator.add_renderer` API at application @@ -267,9 +251,9 @@ class JSONP(JSON): config = Configurator() config.add_renderer('jsonp', JSONP(param_name='callback')) - The class also accepts arbitrary keyword arguments; all keyword arguments - except ``param_name`` are passed to the ``json.dumps`` function as - keyword arguments: + The class' constructor also accepts arbitrary keyword arguments. All + keyword arguments except ``param_name`` are passed to the ``json.dumps`` + function as its keyword arguments. .. code-block:: python @@ -281,6 +265,10 @@ class JSONP(JSON): .. note:: The ability of this class to accept a ``**kw`` in its constructor is new as of Pyramid 1.4. + The arguments passed to this class' constructor mean the same thing as + the arguments passed to :class:`pyramid.renderers.JSON` (including + ``default``). + Once this renderer is registered via :meth:`~pyramid.config.Configurator.add_renderer` as above, you can use ``jsonp`` as the ``renderer=`` parameter to ``@view_config`` or @@ -319,7 +307,7 @@ class JSONP(JSON): plain-JSON encoded string with content-type ``application/json``""" def _render(value, system): request = system['request'] - val = self.value_to_json(value) + val = self._dumps(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 f03c7acda..55ed3f7fd 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -371,12 +371,10 @@ class TestJSON(unittest.TestCase): def test_with_custom_encoder(self): from datetime import datetime - from json import JSONEncoder - class MyEncoder(JSONEncoder): - def default(self, obj): - return obj.isoformat() + def default(obj): + return obj.isoformat() now = datetime.utcnow() - renderer = self._makeOne(cls=MyEncoder)(None) + renderer = self._makeOne(default=default)(None) result = renderer({'a':now}, {}) self.assertEqual(result, '{"a": "%s"}' % now.isoformat()) |
