From 61a378024eb8231290c8bfd0a8977ae9b9065204 Mon Sep 17 00:00:00 2001 From: Christopher Lambacher Date: Mon, 16 Apr 2012 13:15:33 -0400 Subject: Fix a typo in contributors file. --- CONTRIBUTORS.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 -- cgit v1.2.3 From 85d6f81c6b517e6f973fcaba8bcb7df503a16c50 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 16 Apr 2012 22:43:23 -0500 Subject: first cut at removing ObjectJSONEncoder --- pyramid/renderers.py | 72 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 0adadf726..54ba5cf6b 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -4,6 +4,7 @@ import pkg_resources import threading from zope.interface import implementer +from zope.interface.registry import Components from pyramid.interfaces import ( IChameleonLookup, @@ -190,7 +191,7 @@ 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 +199,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 @@ -222,10 +222,19 @@ class JSON(object): """ def __init__(self, **kw): - """ Any keyword arguments will be forwarded to - :func:`json.dumps`. - """ + """ Any keyword arguments will be passed to the encoder + within :meth:`~pyramid.renderers.JSON.dumps`.""" + # we wrap the default with our own + self._default = kw.pop('default', None) + self.kw = kw + self.encoders = Components() + + self._register_default_encoders() + + def _register_default_encoders(self): + import datetime + self.add_encoder(lambda o: o.isoformat(), datetime.datetime) def __call__(self, info): """ Returns a plain JSON-encoded string with content-type @@ -238,16 +247,51 @@ 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 add_encoder(self, encoder, type_or_iface): + """ When an object of type (or interface) ``type_or_iface`` + fails to automatically encode using the default encoder, the + renderer will use the encoder ``encoder`` to convert it into a + string. + + .. code-block:: python + + class Foo(object): + x = 5 + + def foo_encoder(obj): + return str(obj.x) + + renderer = JSON(indent=4) + renderer.add_encoder(foo_encoder, foo) + """ + self.encoders.registerUtility(encoder, type_or_iface) + + def default_encode(self, obj): + """ Encode a custom Python object to a JSON string. + + This should be used by subclasses that have overridden + :meth:`~pyramid.renderers.JSON.dumps` in order to encode objects + that are not otherwise serializable. This should raise a + ``TypeError`` when an object cannot be serialized.""" + if hasattr(obj, '__json__'): + return obj.__json__() + + encoder = self.encoders.queryUtility(obj) + if encoder is not None: + return encoder(obj) + + if self._default is not None: + return self._default(obj) + raise TypeError(repr(obj) + ' is not JSON serializable') + + 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 -- cgit v1.2.3 From 8c44765063c5e969e8afb049fe31c4014500d339 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 16 Apr 2012 22:56:35 -0500 Subject: removed the component registry from json renderer --- pyramid/renderers.py | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 54ba5cf6b..3b1e0f44f 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -4,7 +4,6 @@ import pkg_resources import threading from zope.interface import implementer -from zope.interface.registry import Components from pyramid.interfaces import ( IChameleonLookup, @@ -228,13 +227,6 @@ class JSON(object): self._default = kw.pop('default', None) self.kw = kw - self.encoders = Components() - - self._register_default_encoders() - - def _register_default_encoders(self): - import datetime - self.add_encoder(lambda o: o.isoformat(), datetime.datetime) def __call__(self, info): """ Returns a plain JSON-encoded string with content-type @@ -250,25 +242,6 @@ class JSON(object): return self.dumps(value) return _render - def add_encoder(self, encoder, type_or_iface): - """ When an object of type (or interface) ``type_or_iface`` - fails to automatically encode using the default encoder, the - renderer will use the encoder ``encoder`` to convert it into a - string. - - .. code-block:: python - - class Foo(object): - x = 5 - - def foo_encoder(obj): - return str(obj.x) - - renderer = JSON(indent=4) - renderer.add_encoder(foo_encoder, foo) - """ - self.encoders.registerUtility(encoder, type_or_iface) - def default_encode(self, obj): """ Encode a custom Python object to a JSON string. @@ -279,10 +252,6 @@ class JSON(object): if hasattr(obj, '__json__'): return obj.__json__() - encoder = self.encoders.queryUtility(obj) - if encoder is not None: - return encoder(obj) - if self._default is not None: return self._default(obj) raise TypeError(repr(obj) + ' is not JSON serializable') -- cgit v1.2.3 From dfaf54a7018971c08ed4a437dbec0ffb57d1ff8a Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 16 Apr 2012 22:57:23 -0500 Subject: fixed coverage, removed json encoder --- pyramid/renderers.py | 31 +------------------------------ pyramid/tests/test_renderers.py | 8 +++----- 2 files changed, 4 insertions(+), 35 deletions(-) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 3b1e0f44f..441976ce4 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -157,35 +157,6 @@ 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. @@ -332,7 +303,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()) -- cgit v1.2.3 From 1f0d9d2193bb9557d4475885776b5679c8dbfa23 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 16 Apr 2012 23:19:14 -0500 Subject: docs for json defaults --- docs/api/renderers.rst | 2 -- docs/narr/renderers.rst | 29 +++++++++++++++-------------- 2 files changed, 15 insertions(+), 16 deletions(-) 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..50349c409 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -223,9 +223,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 +260,21 @@ strings, and so forth). # the JSON value returned by ``objects`` will be: # [{"x": 1}, {"x": 2}] -.. note:: +If you don't own the objects being serialized, it's difficult to add a custom +``__json__`` method to the object. In this case, a callback can be supplied +to the renderer which is invoked when other options have failed. - Honoring the ``__json__`` method of custom objects is a feature new in - Pyramid 1.4. +.. code-block:: python + :linenos: + + def default(obj): + if isinstance(obj, datetime.datetime): + return obj.isoformat() + raise TypeError -.. warning:: +.. 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 -- cgit v1.2.3 From 18410a6d9d64786f272268db6368981955ff9f10 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 17 Apr 2012 04:59:33 -0400 Subject: default_encode->_default_encode, dumps->_dumps, massage docs --- docs/narr/renderers.rst | 27 +++++++++++++++++++++++---- pyramid/renderers.py | 44 ++++++++++++++++++++++++-------------------- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 50349c409..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 ~~~~~~~~~~~~~ @@ -260,17 +262,34 @@ strings, and so forth). # the JSON value returned by ``objects`` will be: # [{"x": 1}, {"x": 2}] -If you don't own the objects being serialized, it's difficult to add a custom -``__json__`` method to the object. In this case, a callback can be supplied -to the renderer which is invoked when other options have failed. +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: + from pyramid.renderers import JSON + def default(obj): if isinstance(obj, datetime.datetime): return obj.isoformat() - raise TypeError + 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:: diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 441976ce4..b393a40a6 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -189,14 +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 passed to the encoder - within :meth:`~pyramid.renderers.JSON.dumps`.""" - # we wrap the default with our own + """ 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): @@ -210,35 +216,29 @@ class JSON(object): ct = response.content_type if ct == response.default_content_type: response.content_type = 'application/json' - return self.dumps(value) + return self._dumps(value) return _render - def default_encode(self, obj): - """ Encode a custom Python object to a JSON string. - - This should be used by subclasses that have overridden - :meth:`~pyramid.renderers.JSON.dumps` in order to encode objects - that are not otherwise serializable. This should raise a - ``TypeError`` when an object cannot be serialized.""" + def _default_encode(self, obj): if hasattr(obj, '__json__'): return obj.__json__() if self._default is not None: return self._default(obj) - raise TypeError(repr(obj) + ' is not JSON serializable') + raise TypeError('%r is not JSON serializable' % (obj,)) - def dumps(self, obj): + def _dumps(self, obj): """ Encode a Python object to a JSON string. By default, this uses the :func:`json.dumps` from the stdlib.""" - return json.dumps(obj, default=self.default_encode, **self.kw) + return json.dumps(obj, default=self._default_encode, **self.kw) json_renderer_factory = JSON() # bw compat class JSONP(JSON): """ `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 @@ -251,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 @@ -265,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 @@ -303,7 +307,7 @@ class JSONP(JSON): plain-JSON encoded string with content-type ``application/json``""" def _render(value, system): request = system['request'] - val = self.dumps(value) + val = self._dumps(value) callback = request.GET.get(self.param_name) if callback is None: ct = 'application/json' -- cgit v1.2.3