diff options
| author | Chris McDonough <chrism@plope.com> | 2012-05-03 02:42:55 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2012-05-03 02:42:55 -0400 |
| commit | e012aa12760f6c29bfc9967c50a51d3f47db47da (patch) | |
| tree | ec5f73051a34d4835442c82c115fb91bb452d0ef | |
| parent | c1c2b6ad8e22f9cba291de8903edfa4c7c741dca (diff) | |
| download | pyramid-e012aa12760f6c29bfc9967c50a51d3f47db47da.tar.gz pyramid-e012aa12760f6c29bfc9967c50a51d3f47db47da.tar.bz2 pyramid-e012aa12760f6c29bfc9967c50a51d3f47db47da.zip | |
allow __json__ and custom adapters to accept request arg
| -rw-r--r-- | CHANGES.txt | 3 | ||||
| -rw-r--r-- | docs/narr/renderers.rst | 17 | ||||
| -rw-r--r-- | pyramid/renderers.py | 61 | ||||
| -rw-r--r-- | pyramid/tests/test_renderers.py | 19 |
4 files changed, 64 insertions, 36 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 34d60090d..7c2af4451 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -17,6 +17,9 @@ Features values natively serializable by ``json.dumps`` (such as ints, lists, dictionaries, strings, and so forth). +- The JSON renderer now allows for the definition of custom type adapters to + convert unknown objects to JSON serializations. + - As of this release, the ``request_method`` predicate, when used, will also imply that ``HEAD`` is implied when you use ``GET``. For example, using ``@view_config(request_method='GET')`` is equivalent to using diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index c36caeb87..57b5bc65b 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -241,7 +241,8 @@ 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 JSON-serializable (such as ints, lists, dictionaries, strings, and -so forth). +so forth). It should accept a single additional argument, ``request``, which +will be the active request object at render time. .. code-block:: python :linenos: @@ -252,7 +253,7 @@ so forth). def __init__(self, x): self.x = x - def __json__(self): + def __json__(self, request): return {'x':self.x} @view_config(renderer='json') @@ -269,8 +270,7 @@ 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 custom JSON renderer and adding adapters to handle custom types. The renderer will attempt to adapt non-serializable -objects using the registered adapters. It will raise a :exc:`TypeError` if it -can't determine what to do with the object. A short example follows: +objects using the registered adapters. A short example follows: .. code-block:: python :linenos: @@ -278,12 +278,19 @@ can't determine what to do with the object. A short example follows: from pyramid.renderers import JSON json_renderer = JSON() - json_renderer.add_adapter(datetime.datetime, lambda x: x.isoformat()) + def datetime_adapter(obj, request): + return obj.isoformat() + json_renderer.add_adapter(datetime.datetime, datetime_adapter) # then during configuration .... config = Configurator() config.add_renderer('json', json_renderer) +The adapter should accept two arguments: the object needing to be serialized +and ``request``, which will be the current request object at render time. +The adapter should raise a :exc:`TypeError` if it can't determine what to do +with the object. + See :class:`pyramid.renderers.JSON` and :ref:`adding_and_overriding_renderers` for more information. diff --git a/pyramid/renderers.py b/pyramid/renderers.py index ad09ad927..bdef6f561 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -3,7 +3,10 @@ import os import pkg_resources import threading -from zope.interface import implementer +from zope.interface import ( + implementer, + providedBy, + ) from zope.interface.registry import Components from pyramid.interfaces import ( @@ -215,17 +218,18 @@ class JSON(object): self.add_adapter(type, adapter) def add_adapter(self, type_or_iface, adapter): - """ When an object of type (or interface) ``type_or_iface`` - fails to automatically encode using the serializer, the renderer - will use the adapter ``adapter`` to convert it into a - JSON-serializable object. + """ When an object of type (or interface) ``type_or_iface`` fails to + automatically encode using the serializer, the renderer will use the + adapter ``adapter`` to convert it into a JSON-serializable object. + The adapter must accept two arguments: the object and the currently + active request. .. code-block:: python class Foo(object): x = 5 - def foo_adapter(obj): + def foo_adapter(obj, request): return obj.x renderer = JSON(indent=4) @@ -245,25 +249,21 @@ class JSON(object): ct = response.content_type if ct == response.default_content_type: response.content_type = 'application/json' - return self.serializer(value, default=self.default, **self.kw) - return _render - - def default(self, obj): - """ Returns a JSON-serializable representation of ``obj``. If - no representation can be found, a ``TypeError`` is raised. - If the object implements the ``__json__`` magic method, it will - be preferred. Otherwise, attempt to adapt ``obj`` into a - serializable type using one of the registered adapters. - """ - if hasattr(obj, '__json__'): - return obj.__json__() - - result = self.components.queryAdapter(obj, IJSONAdapter, - default=_marker) - if result is not _marker: - return result - raise TypeError('%r is not JSON serializable' % (obj,)) + def default(obj): + if hasattr(obj, '__json__'): + return obj.__json__(request) + obj_iface = providedBy(obj) + adapters = self.components.adapters + result = adapters.lookup((obj_iface,), IJSONAdapter, + default=_marker) + if result is _marker: + raise TypeError('%r is not JSON serializable' % (obj,)) + return result(obj, request) + + return self.serializer(value, default=default, **self.kw) + + return _render json_renderer_factory = JSON() # bw compat @@ -339,7 +339,18 @@ class JSONP(JSON): plain-JSON encoded string with content-type ``application/json``""" def _render(value, system): request = system['request'] - val = self.serializer(value, default=self.default, **self.kw) + + def default(obj): + if hasattr(obj, '__json__'): + return obj.__json__(request) + + result = self.components.queryAdapter(obj, IJSONAdapter, + default=_marker) + if result is not _marker: + return result + raise TypeError('%r is not JSON serializable' % (obj,)) + + val = self.serializer(value, default=default, **self.kw) 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 55c5c2f5a..495d7dc23 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -370,22 +370,26 @@ class TestJSON(unittest.TestCase): self.assertEqual(request.response.content_type, 'text/mishmash') def test_with_custom_adapter(self): + request = testing.DummyRequest() from datetime import datetime - def adapter(obj): + def adapter(obj, req): + self.assertEqual(req, request) return obj.isoformat() now = datetime.utcnow() renderer = self._makeOne() renderer.add_adapter(datetime, adapter) - result = renderer(None)({'a':now}, {}) + result = renderer(None)({'a':now}, {'request':request}) self.assertEqual(result, '{"a": "%s"}' % now.isoformat()) def test_with_custom_adapter2(self): + request = testing.DummyRequest() from datetime import datetime - def adapter(obj): + def adapter(obj, req): + self.assertEqual(req, request) return obj.isoformat() now = datetime.utcnow() renderer = self._makeOne(adapters=((datetime, adapter),)) - result = renderer(None)({'a':now}, {}) + result = renderer(None)({'a':now}, {'request':request}) self.assertEqual(result, '{"a": "%s"}' % now.isoformat()) def test_with_custom_serializer(self): @@ -404,15 +408,18 @@ class TestJSON(unittest.TestCase): self.assertTrue('default' in serializer.kw) def test_with_object_adapter(self): + request = testing.DummyRequest() + outerself = self class MyObject(object): def __init__(self, x): self.x = x - def __json__(self): + def __json__(self, req): + outerself.assertEqual(req, request) return {'x': self.x} objects = [MyObject(1), MyObject(2)] renderer = self._makeOne()(None) - result = renderer(objects, {}) + result = renderer(objects, {'request':request}) self.assertEqual(result, '[{"x": 1}, {"x": 2}]') def test_with_object_adapter_no___json__(self): |
