diff options
| -rw-r--r-- | CONTRIBUTORS.txt | 2 | ||||
| -rw-r--r-- | TODO.txt | 3 | ||||
| -rw-r--r-- | docs/api/renderers.rst | 2 | ||||
| -rw-r--r-- | docs/glossary.rst | 4 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 6 | ||||
| -rw-r--r-- | docs/narr/renderers.rst | 48 | ||||
| -rw-r--r-- | pyramid/renderers.py | 84 | ||||
| -rw-r--r-- | pyramid/tests/test_renderers.py | 8 | ||||
| -rw-r--r-- | setup.py | 2 |
9 files changed, 83 insertions, 76 deletions
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 @@ -4,6 +4,9 @@ Pyramid TODOs Nice-to-Have ------------ +- Provide the presumed renderer name to the called view as an attribute of + the request. + - Have action methods return their discriminators. - Add docs about upgrading between Pyramid versions (e.g. how to see 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/glossary.rst b/docs/glossary.rst index 60920a73a..88598354a 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -290,7 +290,7 @@ Glossary :term:`principal` (or principals) associated with a request. WSGI - `Web Server Gateway Interface <http://wsgi.org/>`_. This is a + `Web Server Gateway Interface <http://www.wsgi.org/>`_. This is a Python standard for connecting web applications to web servers, similar to the concept of Java Servlets. :app:`Pyramid` requires that your application be served as a WSGI application. @@ -299,7 +299,7 @@ Glossary *Middleware* is a :term:`WSGI` concept. It is a WSGI component that acts both as a server and an application. Interesting uses for middleware exist, such as caching, content-transport - encoding, and other functions. See `WSGI.org <http://wsgi.org>`_ + encoding, and other functions. See `WSGI.org <http://www.wsgi.org>`_ or `PyPI <http://python.org/pypi>`_ to find middleware for your application. diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index b6e3dd163..a2143b3c5 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -145,7 +145,7 @@ the view which generates it can be overridden as necessary. The :term:`forbidden view` callable is a view callable like any other. The :term:`view configuration` which causes it to be a "forbidden" view consists -of using the meth:`pyramid.config.Configurator.add_forbidden_view` API or the +of using the :meth:`pyramid.config.Configurator.add_forbidden_view` API or the :class:`pyramid.view.forbidden_view_config` decorator. For example, you can add a forbidden view by using the @@ -171,7 +171,7 @@ as a forbidden view: from pyramid.view import forbidden_view_config - forbidden_view_config() + @forbidden_view_config() def forbidden(request): return Response('forbidden') @@ -625,7 +625,7 @@ converts the arbitrary return value into something that implements :class:`~pyramid.interfaces.IResponse`. For example, if you'd like to allow view callables to return bare string -objects (without requiring a a :term:`renderer` to convert a string to a +objects (without requiring a :term:`renderer` to convert a string to a response object), you can register an adapter which converts the string to a Response: diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 34bee3c7f..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 ~~~~~~~~~~~~~ @@ -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 diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 0adadf726..b393a40a6 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -157,40 +157,11 @@ 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. 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 +169,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 @@ -219,12 +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 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 @@ 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 _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 @@ 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()) @@ -77,7 +77,7 @@ setup(name='pyramid', "Programming Language :: Python :: 3.2", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", - "Framework :: Pylons", + "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI", "License :: Repoze Public License", |
