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

This commit is contained in:
Armin Ronacher 2012-10-07 23:31:48 +02:00
parent 301e244df3
commit b146d8277a
9 changed files with 227 additions and 84 deletions

View file

@ -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

View file

@ -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):

View file

@ -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.

View file

@ -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 Normal file
View file

@ -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')

View file

@ -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

View file

@ -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