Permalink
Browse files

Merge branch 'mmerickel-feature.json-api'

  • Loading branch information...
2 parents 04e7169 + 18410a6 commit c8aab32b60706f700b7f6a70d967727c353e3d54 @mcdonc mcdonc committed Apr 17, 2012
Showing with 74 additions and 70 deletions.
  1. +1 −1 CONTRIBUTORS.txt
  2. +0 −2 docs/api/renderers.rst
  3. +34 −14 docs/narr/renderers.rst
  4. +36 −48 pyramid/renderers.py
  5. +3 −5 pyramid/tests/test_renderers.py
View
2 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
View
2 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
View
48 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
View
84 pyramid/renderers.py
@@ -157,53 +157,23 @@ def _render(value, system):
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
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 @@ def myview(request):
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 @@ def _render(value, system):
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 @@ def __call__(self, info):
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'
View
8 pyramid/tests/test_renderers.py
@@ -371,12 +371,10 @@ def test_with_request_content_type_set(self):
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())

0 comments on commit c8aab32

Please sign in to comment.