Browse Source

Added wrapper module around simplejson/json for much simplified customization.

pull/609/head
Armin Ronacher 12 years ago
parent
commit
b146d8277a
  1. 3
      CHANGES
  2. 72
      docs/api.rst
  3. 14
      flask/__init__.py
  4. 16
      flask/app.py
  5. 3
      flask/exceptions.py
  6. 46
      flask/helpers.py
  7. 146
      flask/json.py
  8. 3
      flask/sessions.py
  9. 2
      flask/wrappers.py

3
CHANGES

@ -17,6 +17,9 @@ Release date to be decided.
- ``tojson`` filter now does not escape script blocks in HTML5 parsers.
- Flask will now raise an error if you attempt to register a new function
on an already used endpoint.
- Added wrapper module around simplejson and added default serialization
of datetime objects. This allows much easier customization of how
JSON is handled by Flask or any Flask extension.
Version 0.9
-----------

72
docs/api.rst

@ -312,43 +312,71 @@ Message Flashing
.. autofunction:: get_flashed_messages
Returning JSON
--------------
JSON Support
------------
.. autofunction:: jsonify
.. module:: flask.json
Flask uses ``simplejson`` for the JSON implementation. Since simplejson
is provided both by the standard library as well as extension Flask will
try simplejson first and then fall back to the stdlib json module. On top
of that it will delegate access to the current application's JSOn encoders
and decoders for easier customization.
So for starters instead of doing::
.. data:: json
try:
import simplejson as json
except ImportError:
import json
If JSON support is picked up, this will be the module that Flask is
using to parse and serialize JSON. So instead of doing this yourself::
You can instead just do this::
try:
import simplejson as json
except ImportError:
import json
from flask import json
You can instead just do this::
For usage examples, read the :mod:`json` documentation in the standard
lirbary. The following extensions are by default applied to the stdlib's
JSON module:
from flask import json
1. ``datetime`` objects are serialized as :rfc:`822` strings.
2. Any object with an ``__html__`` method (like :class:`~flask.Markup`)
will ahve that method called and then the return value is serialized
as string.
For usage examples, read the :mod:`json` documentation.
The :func:`~htmlsafe_dumps` function of this json module is also available
as filter called ``|tojson`` in Jinja2. Note that inside `script`
tags no escaping must take place, so make sure to disable escaping
with ``|safe`` if you intend to use it inside `script` tags:
The :func:`~json.dumps` function of this json module is also available
as filter called ``|tojson`` in Jinja2. Note that inside `script`
tags no escaping must take place, so make sure to disable escaping
with ``|safe`` if you intend to use it inside `script` tags:
.. sourcecode:: html+jinja
<script type=text/javascript>
doSomethingWith({{ user.username|tojson|safe }});
</script>
Note that the ``|tojson`` filter escapes forward slashes properly.
.. autofunction:: jsonify
.. sourcecode:: html+jinja
.. autofunction:: dumps
<script type=text/javascript>
doSomethingWith({{ user.username|tojson|safe }});
</script>
.. autofunction:: dump
Note that the ``|tojson`` filter escapes forward slashes properly.
.. autofunction:: loads
.. autofunction:: load
.. autoclass:: JSONEncoder
:members:
.. autoclass:: JSONDecoder
:members:
Template Rendering
------------------
.. currentmodule:: flask
.. autofunction:: render_template
.. autofunction:: render_template_string

14
flask/__init__.py

@ -20,9 +20,8 @@ from jinja2 import Markup, escape
from .app import Flask, Request, Response
from .config import Config
from .helpers import url_for, jsonify, flash, \
send_file, send_from_directory, get_flashed_messages, \
get_template_attribute, make_response, safe_join, \
from .helpers import url_for, flash, send_file, send_from_directory, \
get_flashed_messages, get_template_attribute, make_response, safe_join, \
stream_with_context
from .globals import current_app, g, request, session, _request_ctx_stack, \
_app_ctx_stack
@ -37,8 +36,13 @@ from .signals import signals_available, template_rendered, request_started, \
request_finished, got_request_exception, request_tearing_down, \
appcontext_tearing_down
# only import json if it's available
from .helpers import json
# We're not exposing the actual json module but a convenient wrapper around
# it.
from . import json
# This was the only thing that flask used to export at one point and it had
# a more generic name.
jsonify = json.jsonify
# backwards compat, goes away in 1.0
from .sessions import SecureCookieSession as Session

16
flask/app.py

@ -24,8 +24,8 @@ from werkzeug.exceptions import HTTPException, InternalServerError, \
MethodNotAllowed, BadRequest
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
locked_cached_property, _tojson_filter, _endpoint_from_view_func, \
find_package
locked_cached_property, _endpoint_from_view_func, find_package
from . import json
from .wrappers import Request, Response
from .config import ConfigAttribute, Config
from .ctx import RequestContext, AppContext, _RequestGlobals
@ -238,6 +238,16 @@ class Flask(_PackageBoundObject):
'-' * 80
)
#: The JSON encoder class to use. Defaults to :class:`~flask.json.JSONEncoder`.
#:
#: .. versionadded:: 0.10
json_encoder = json.JSONEncoder
#: The JSON decoder class to use. Defaults to :class:`~flask.json.JSONDecoder`.
#:
#: .. versionadded:: 0.10
json_decoder = json.JSONDecoder
#: Options that are passed directly to the Jinja2 environment.
jinja_options = ImmutableDict(
extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
@ -637,7 +647,7 @@ class Flask(_PackageBoundObject):
url_for=url_for,
get_flashed_messages=get_flashed_messages
)
rv.filters['tojson'] = _tojson_filter
rv.filters['tojson'] = json.htmlsafe_dumps
return rv
def create_global_jinja_loader(self):

3
flask/exceptions.py

@ -9,7 +9,7 @@
:license: BSD, see LICENSE for more details.
"""
from werkzeug.exceptions import HTTPException, BadRequest
from .helpers import json
from . import json
class JSONHTTPException(HTTPException):
@ -39,7 +39,6 @@ class JSONHTTPException(HTTPException):
class JSONBadRequest(JSONHTTPException, BadRequest):
"""Represents an HTTP ``400 Bad Request`` error whose body contains an
error message in JSON format instead of HTML format (as in the superclass).
"""
#: The description of the error which occurred as a string.

46
flask/helpers.py

@ -23,10 +23,6 @@ from werkzeug.routing import BuildError
from werkzeug.urls import url_quote
from functools import update_wrapper
# Use the same json implementation as itsdangerous on which we
# depend anyways.
from itsdangerous import simplejson as json
from werkzeug.datastructures import Headers
from werkzeug.exceptions import NotFound
@ -43,17 +39,6 @@ from .globals import session, _request_ctx_stack, _app_ctx_stack, \
current_app, request
# figure out if simplejson escapes slashes. This behavior was changed
# from one version to another without reason.
_slash_escape = '\\/' not in json.dumps('/')
def _tojson_filter(*args, **kwargs):
rv = json.dumps(*args, **kwargs)
if _slash_escape:
rv = rv.replace('/', '\\/')
return rv.replace('<!', '<\\u0021')
# sentinel
_missing = object()
@ -146,37 +131,6 @@ def stream_with_context(generator_or_function):
return wrapped_g
def jsonify(*args, **kwargs):
"""Creates a :class:`~flask.Response` with the JSON representation of
the given arguments with an `application/json` mimetype. The arguments
to this function are the same as to the :class:`dict` constructor.
Example usage::
@app.route('/_get_current_user')
def get_current_user():
return jsonify(username=g.user.username,
email=g.user.email,
id=g.user.id)
This will send a JSON response like this to the browser::
{
"username": "admin",
"email": "admin@localhost",
"id": 42
}
This requires Python 2.6 or an installed version of simplejson. For
security reasons only objects are supported toplevel. For more
information about this, have a look at :ref:`json-security`.
.. versionadded:: 0.2
"""
return current_app.response_class(json.dumps(dict(*args, **kwargs),
indent=None if request.is_xhr else 2), mimetype='application/json')
def make_response(*args):
"""Sometimes it is necessary to set additional headers in a view. Because
views do not have to return response objects but can return a value that

146
flask/json.py

@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
"""
flask.jsonimpl
~~~~~~~~~~~~~~
Implementation helpers for the JSON support in Flask.
:copyright: (c) 2012 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from datetime import datetime
from .globals import current_app, request
from werkzeug.http import http_date
# Use the same json implementation as itsdangerous on which we
# depend anyways.
from itsdangerous import simplejson as _json
# figure out if simplejson escapes slashes. This behavior was changed
# from one version to another without reason.
_slash_escape = '\\/' not in _json.dumps('/')
__all__ = ['dump', 'dumps', 'load', 'loads', 'htmlsafe_dump',
'htmlsafe_dumps', 'JSONDecoder', 'JSONEncoder',
'jsonify']
class JSONEncoder(_json.JSONEncoder):
"""The default Flask JSON encoder. This one extends the default simplejson
encoder by also supporting ``datetime`` objects as well as ``Markup``
objects which are serialized as RFC 822 datetime strings (same as the HTTP
date format). In order to support more data types override the
:meth:`default` method.
"""
def default(self, o):
"""Implement this method in a subclass such that it returns a
serializable object for ``o``, or calls the base implementation (to
raise a ``TypeError``).
For example, to support arbitrary iterators, you could implement
default like this::
def default(self, o):
try:
iterable = iter(o)
except TypeError:
pass
else:
return list(iterable)
return JSONEncoder.default(self, o)
"""
if isinstance(o, datetime):
return http_date(o)
if hasattr(o, '__html__'):
return unicode(o.__html__())
return _json.JSONEncoder.default(self, o)
class JSONDecoder(_json.JSONDecoder):
"""The default JSON decoder. This one does not change the behavior from
the default simplejson encoder. Consult the :mod:`json` documentation
for more information. This decoder is not only used for the load
functions of this module but also :attr:`~flask.Request`.
"""
def dumps(obj, **kwargs):
"""Serialize ``obj`` to a JSON formatted ``str`` by using the application's
configured encoder (:attr:`~flask.Flask.json_encoder`).
"""
kwargs.setdefault('cls', current_app.json_encoder)
return _json.dumps(obj, **kwargs)
def dump(obj, fp, **kwargs):
"""Like :func:`dumps` but writes into a file object."""
kwargs.setdefault('cls', current_app.json_encoder)
return _json.dump(obj, fp, **kwargs)
def loads(s, **kwargs):
"""Unserialize a JSON object from a string ``s`` by using the application's
configured decoder (:attr:`~flask.Flask.json_decoder`).
"""
kwargs.setdefault('cls', current_app.json_decoder)
return _json.loads(s, **kwargs)
def load(fp, **kwargs):
"""Like :func:`loads` but reads from a file object.
"""
kwargs.setdefault('cls', current_app.json_decoder)
return _json.load(fp, **kwargs)
def htmlsafe_dumps(obj, **kwargs):
"""Works exactly like :func:`dumps` but is safe for use in ``<script>``
tags. It accepts the same arguments and returns a JSON string. Note that
this is available in templates through the ``|tojson`` filter but it will
have to be wrapped in ``|safe`` unless **true** XHTML is being used.
"""
rv = dumps(obj, **kwargs)
if _slash_escape:
rv = rv.replace('/', '\\/')
return rv.replace('<!', '<\\u0021')
def htmlsafe_dump(obj, fp, **kwargs):
"""Like :func:`htmlsafe_dumps` but writes into a file object."""
fp.write(htmlsafe_dumps(obj, **kwargs))
def jsonify(*args, **kwargs):
"""Creates a :class:`~flask.Response` with the JSON representation of
the given arguments with an `application/json` mimetype. The arguments
to this function are the same as to the :class:`dict` constructor.
Example usage::
@app.route('/_get_current_user')
def get_current_user():
return jsonify(username=g.user.username,
email=g.user.email,
id=g.user.id)
This will send a JSON response like this to the browser::
{
"username": "admin",
"email": "admin@localhost",
"id": 42
}
This requires Python 2.6 or an installed version of simplejson. For
security reasons only objects are supported toplevel. For more
information about this, have a look at :ref:`json-security`.
.. versionadded:: 0.2
"""
return current_app.response_class(dumps(dict(*args, **kwargs),
indent=None if request.is_xhr else 2),
mimetype='application/json')

3
flask/sessions.py

@ -13,8 +13,7 @@ import hashlib
from datetime import datetime
from werkzeug.http import http_date, parse_date
from werkzeug.datastructures import CallbackDict
from .helpers import json
from . import Markup
from . import Markup, json
from itsdangerous import URLSafeTimedSerializer, BadSignature

2
flask/wrappers.py

@ -14,7 +14,7 @@ from werkzeug.utils import cached_property
from .exceptions import JSONBadRequest
from .debughelpers import attach_enctype_error_multidict
from .helpers import json
from . import json
from .globals import _request_ctx_stack

Loading…
Cancel
Save