diff options
| -rw-r--r-- | CHANGES.txt | 21 | ||||
| -rw-r--r-- | docs/narr/renderers.rst | 68 | ||||
| -rw-r--r-- | pyramid/renderers.py | 47 | ||||
| -rw-r--r-- | pyramid/tests/test_renderers.py | 12 |
4 files changed, 108 insertions, 40 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 859dc7b74..0714f6940 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,22 +1,13 @@ Next release ============ -Bug Fixes ---------- +Features +-------- -- Add ``REMOTE_ADDR`` to the ``prequest`` WSGI environ dict for benefit of - the debug toolbar, which effectively requires it to be present to work - properly. - -- When an asset specification was used as a Mako ``renderer=`` argument and a - ``mako.modules_directory`` was specified, Pyramid would fail to render the - template and instead would raise an error when attempting to write the file - to the modules directory. Example symptom: ``WindowsError: [Error 267] The - directory name is invalid: - 'c:\\docume~1\\chrism\\locals~1\\temp\\tmp9jtjix\\pyramid.tests:fixtures'``. - We now replace the colon in the Mako module filename with a dollar sign, so - it can work on Windows. See https://github.com/Pylons/pyramid/issues/512 - for more information. +- Custom objects can be made easily JSON-serializable in Pyramid by defining + a ``__json__`` method on the object's class. This method should return + values natively serializable by ``json.dumps`` (such as ints, lists, + dictionaries, strings, and so forth). 1.3 (2012-03-21) ================ diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 52e97d091..34bee3c7f 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -207,17 +207,13 @@ 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`. 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. - -Custom objects can be easily serialized by defining a :func:`__json__` method -on the object. This method should return values serializable by -:func:`json_dumps`. By defining this method and using a :term:`JSON` -renderer the :class:`pyramid.renderers.ObjectJSONEncoder` class will be used -for encoding your object. If you later define your own custom encoder it will -override :class:`pyramid.renderers.ObjectJSONEncoder`. +values serializable by ``json.dumps``. + +.. note:: + + Extra arguments can be passed to ``json.dumps`` by overriding the default + ``json`` 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 @@ -235,13 +231,57 @@ 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`. +.. _json_serializing_custom_objects: + +Serializing Custom Objects +++++++++++++++++++++++++++ + +Custom objects can be made easily JSON-serializable in Pyramid by defining a +``__json__`` method on the object's class. This method should return values +natively serializable by ``json.dumps`` (such as ints, lists, dictionaries, +strings, and so forth). + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + + class MyObject(object): + def __init__(self, x): + self.x = x + + def __json__(self): + return {'x':self.x} + + @view_config(renderer='json') + def objects(request): + return [MyObject(1), MyObject(2)] + + # the JSON value returned by ``objects`` will be: + # [{"x": 1}, {"x": 2}] + +.. note:: + + Honoring the ``__json__`` method of custom objects is a feature new in + Pyramid 1.4. + +.. warning:: + + 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`. + .. index:: pair: renderer; JSONP .. _jsonp_renderer: JSONP Renderer --------------- +~~~~~~~~~~~~~~ .. note:: This feature is new in Pyramid 1.1. @@ -306,6 +346,10 @@ The string ``callback=?`` above in the the ``url`` param to the JQuery a JSONP request; the ``callback`` parameter will be automatically filled in for you and used. +The same custom-object serialization scheme defined used for a "normal" JSON +renderer in :ref:`json_serializing_custom_objects` can be used when passing +values to a JSONP renderer too. + .. index:: pair: renderer; chameleon diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 859fd3953..0adadf726 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -158,21 +158,31 @@ def string_renderer_factory(info): return _render class ObjectJSONEncoder(json.JSONEncoder): - """ Encoder that is used by :class:`pyramid.renderers.JSON` and - :class:`pyramid.renderers.JSONP`. + """ 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 encoder will look for a :meth:`__json__` method on an object - and use the return value when encoding the object to json - using :meth:`json_dumps`. + 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.3. + .. note:: This feature is new in Pyramid 1.4. """ def default(self, obj): - if hasattr(obj, '__json__') and callable(obj.__json__): + if hasattr(obj, '__json__'): return obj.__json__() return json.JSONEncoder.default(self, obj) @@ -203,9 +213,12 @@ class JSON(object): 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. + .. note:: + + This feature is new in Pyramid 1.4. Prior to 1.4 there was + no public API for supplying options to the underlying + :func:`json.dumps` without defining a custom renderer. + """ def __init__(self, **kw): @@ -254,6 +267,20 @@ 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: + + .. code-block:: python + + from pyramid.config import Configurator + + config = Configurator() + config.add_renderer('jsonp', JSONP(param_name='callback', indent=4)) + + .. note:: The ability of this class to accept a ``**kw`` in its + constructor is new as of Pyramid 1.4. + 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 diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 9d9e608f1..f03c7acda 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -374,9 +374,7 @@ class TestJSON(unittest.TestCase): from json import JSONEncoder class MyEncoder(JSONEncoder): def default(self, obj): - if isinstance(obj, datetime): - return obj.isoformat() - return super(JSONEncoder, self).default(obj) + return obj.isoformat() now = datetime.utcnow() renderer = self._makeOne(cls=MyEncoder)(None) result = renderer({'a':now}, {}) @@ -394,6 +392,14 @@ class TestJSON(unittest.TestCase): result = renderer(objects, {}) self.assertEqual(result, '[{"x": 1}, {"x": 2}]') + def test_with_object_encoder_no___json__(self): + class MyObject(object): + def __init__(self, x): + self.x = x + objects = [MyObject(1), MyObject(2)] + renderer = self._makeOne()(None) + self.assertRaises(TypeError, renderer, objects, {}) + class Test_string_renderer_factory(unittest.TestCase): def _callFUT(self, name): from pyramid.renderers import string_renderer_factory |
