summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt3
-rw-r--r--docs/narr/renderers.rst17
-rw-r--r--pyramid/renderers.py61
-rw-r--r--pyramid/tests/test_renderers.py19
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):