forked from orbit-oss/flask
Added wrapper module around simplejson/json for much simplified customization.
This commit is contained in:
parent
301e244df3
commit
b146d8277a
9 changed files with 227 additions and 84 deletions
3
CHANGES
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
|
||||
-----------
|
||||
|
|
|
|||
78
docs/api.rst
78
docs/api.rst
|
|
@ -312,43 +312,71 @@ Message Flashing
|
|||
|
||||
.. autofunction:: get_flashed_messages
|
||||
|
||||
Returning JSON
|
||||
--------------
|
||||
JSON Support
|
||||
------------
|
||||
|
||||
.. 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::
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
You can instead just do this::
|
||||
|
||||
from flask import json
|
||||
|
||||
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:
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<script type=text/javascript>
|
||||
doSomethingWith({{ user.username|tojson|safe }});
|
||||
</script>
|
||||
|
||||
Note that the ``|tojson`` filter escapes forward slashes properly.
|
||||
|
||||
.. autofunction:: jsonify
|
||||
|
||||
.. data:: json
|
||||
.. autofunction:: dumps
|
||||
|
||||
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::
|
||||
.. autofunction:: dump
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
.. autofunction:: loads
|
||||
|
||||
You can instead just do this::
|
||||
.. autofunction:: load
|
||||
|
||||
from flask import json
|
||||
.. autoclass:: JSONEncoder
|
||||
:members:
|
||||
|
||||
For usage examples, read the :mod:`json` documentation.
|
||||
|
||||
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.
|
||||
.. autoclass:: JSONDecoder
|
||||
:members:
|
||||
|
||||
Template Rendering
|
||||
------------------
|
||||
|
||||
.. currentmodule:: flask
|
||||
|
||||
.. autofunction:: render_template
|
||||
|
||||
.. autofunction:: render_template_string
|
||||
|
|
|
|||
|
|
@ -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
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):
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
146
flask/json.py
Normal 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')
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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…
Add table
Add a link
Reference in a new issue