forked from s434596/CatOrNot
328 lines
10 KiB
Python
328 lines
10 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
"""
|
||
|
flask.json
|
||
|
~~~~~~~~~~
|
||
|
|
||
|
:copyright: © 2010 by the Pallets team.
|
||
|
:license: BSD, see LICENSE for more details.
|
||
|
"""
|
||
|
import codecs
|
||
|
import io
|
||
|
import uuid
|
||
|
from datetime import date, datetime
|
||
|
from flask.globals import current_app, request
|
||
|
from flask._compat import text_type, PY2
|
||
|
|
||
|
from werkzeug.http import http_date
|
||
|
from jinja2 import Markup
|
||
|
|
||
|
# Use the same json implementation as itsdangerous on which we
|
||
|
# depend anyways.
|
||
|
from itsdangerous import json 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']
|
||
|
|
||
|
|
||
|
def _wrap_reader_for_text(fp, encoding):
|
||
|
if isinstance(fp.read(0), bytes):
|
||
|
fp = io.TextIOWrapper(io.BufferedReader(fp), encoding)
|
||
|
return fp
|
||
|
|
||
|
|
||
|
def _wrap_writer_for_text(fp, encoding):
|
||
|
try:
|
||
|
fp.write('')
|
||
|
except TypeError:
|
||
|
fp = io.TextIOWrapper(fp, encoding)
|
||
|
return fp
|
||
|
|
||
|
|
||
|
class JSONEncoder(_json.JSONEncoder):
|
||
|
"""The default Flask JSON encoder. This one extends the default simplejson
|
||
|
encoder by also supporting ``datetime`` objects, ``UUID`` 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 :exc:`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.utctimetuple())
|
||
|
if isinstance(o, date):
|
||
|
return http_date(o.timetuple())
|
||
|
if isinstance(o, uuid.UUID):
|
||
|
return str(o)
|
||
|
if hasattr(o, '__html__'):
|
||
|
return text_type(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 decoder. 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 _dump_arg_defaults(kwargs):
|
||
|
"""Inject default arguments for dump functions."""
|
||
|
if current_app:
|
||
|
bp = current_app.blueprints.get(request.blueprint) if request else None
|
||
|
kwargs.setdefault(
|
||
|
'cls',
|
||
|
bp.json_encoder if bp and bp.json_encoder
|
||
|
else current_app.json_encoder
|
||
|
)
|
||
|
|
||
|
if not current_app.config['JSON_AS_ASCII']:
|
||
|
kwargs.setdefault('ensure_ascii', False)
|
||
|
|
||
|
kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS'])
|
||
|
else:
|
||
|
kwargs.setdefault('sort_keys', True)
|
||
|
kwargs.setdefault('cls', JSONEncoder)
|
||
|
|
||
|
|
||
|
def _load_arg_defaults(kwargs):
|
||
|
"""Inject default arguments for load functions."""
|
||
|
if current_app:
|
||
|
bp = current_app.blueprints.get(request.blueprint) if request else None
|
||
|
kwargs.setdefault(
|
||
|
'cls',
|
||
|
bp.json_decoder if bp and bp.json_decoder
|
||
|
else current_app.json_decoder
|
||
|
)
|
||
|
else:
|
||
|
kwargs.setdefault('cls', JSONDecoder)
|
||
|
|
||
|
|
||
|
def detect_encoding(data):
|
||
|
"""Detect which UTF codec was used to encode the given bytes.
|
||
|
|
||
|
The latest JSON standard (:rfc:`8259`) suggests that only UTF-8 is
|
||
|
accepted. Older documents allowed 8, 16, or 32. 16 and 32 can be big
|
||
|
or little endian. Some editors or libraries may prepend a BOM.
|
||
|
|
||
|
:param data: Bytes in unknown UTF encoding.
|
||
|
:return: UTF encoding name
|
||
|
"""
|
||
|
head = data[:4]
|
||
|
|
||
|
if head[:3] == codecs.BOM_UTF8:
|
||
|
return 'utf-8-sig'
|
||
|
|
||
|
if b'\x00' not in head:
|
||
|
return 'utf-8'
|
||
|
|
||
|
if head in (codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE):
|
||
|
return 'utf-32'
|
||
|
|
||
|
if head[:2] in (codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE):
|
||
|
return 'utf-16'
|
||
|
|
||
|
if len(head) == 4:
|
||
|
if head[:3] == b'\x00\x00\x00':
|
||
|
return 'utf-32-be'
|
||
|
|
||
|
if head[::2] == b'\x00\x00':
|
||
|
return 'utf-16-be'
|
||
|
|
||
|
if head[1:] == b'\x00\x00\x00':
|
||
|
return 'utf-32-le'
|
||
|
|
||
|
if head[1::2] == b'\x00\x00':
|
||
|
return 'utf-16-le'
|
||
|
|
||
|
if len(head) == 2:
|
||
|
return 'utf-16-be' if head.startswith(b'\x00') else 'utf-16-le'
|
||
|
|
||
|
return 'utf-8'
|
||
|
|
||
|
|
||
|
def dumps(obj, **kwargs):
|
||
|
"""Serialize ``obj`` to a JSON formatted ``str`` by using the application's
|
||
|
configured encoder (:attr:`~flask.Flask.json_encoder`) if there is an
|
||
|
application on the stack.
|
||
|
|
||
|
This function can return ``unicode`` strings or ascii-only bytestrings by
|
||
|
default which coerce into unicode strings automatically. That behavior by
|
||
|
default is controlled by the ``JSON_AS_ASCII`` configuration variable
|
||
|
and can be overridden by the simplejson ``ensure_ascii`` parameter.
|
||
|
"""
|
||
|
_dump_arg_defaults(kwargs)
|
||
|
encoding = kwargs.pop('encoding', None)
|
||
|
rv = _json.dumps(obj, **kwargs)
|
||
|
if encoding is not None and isinstance(rv, text_type):
|
||
|
rv = rv.encode(encoding)
|
||
|
return rv
|
||
|
|
||
|
|
||
|
def dump(obj, fp, **kwargs):
|
||
|
"""Like :func:`dumps` but writes into a file object."""
|
||
|
_dump_arg_defaults(kwargs)
|
||
|
encoding = kwargs.pop('encoding', None)
|
||
|
if encoding is not None:
|
||
|
fp = _wrap_writer_for_text(fp, encoding)
|
||
|
_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`) if there is an
|
||
|
application on the stack.
|
||
|
"""
|
||
|
_load_arg_defaults(kwargs)
|
||
|
if isinstance(s, bytes):
|
||
|
encoding = kwargs.pop('encoding', None)
|
||
|
if encoding is None:
|
||
|
encoding = detect_encoding(s)
|
||
|
s = s.decode(encoding)
|
||
|
return _json.loads(s, **kwargs)
|
||
|
|
||
|
|
||
|
def load(fp, **kwargs):
|
||
|
"""Like :func:`loads` but reads from a file object.
|
||
|
"""
|
||
|
_load_arg_defaults(kwargs)
|
||
|
if not PY2:
|
||
|
fp = _wrap_reader_for_text(fp, kwargs.pop('encoding', None) or 'utf-8')
|
||
|
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 which will
|
||
|
also mark the result as safe. Due to how this function escapes certain
|
||
|
characters this is safe even if used outside of ``<script>`` tags.
|
||
|
|
||
|
The following characters are escaped in strings:
|
||
|
|
||
|
- ``<``
|
||
|
- ``>``
|
||
|
- ``&``
|
||
|
- ``'``
|
||
|
|
||
|
This makes it safe to embed such strings in any place in HTML with the
|
||
|
notable exception of double quoted attributes. In that case single
|
||
|
quote your attributes or HTML escape it in addition.
|
||
|
|
||
|
.. versionchanged:: 0.10
|
||
|
This function's return value is now always safe for HTML usage, even
|
||
|
if outside of script tags or if used in XHTML. This rule does not
|
||
|
hold true when using this function in HTML attributes that are double
|
||
|
quoted. Always single quote attributes if you use the ``|tojson``
|
||
|
filter. Alternatively use ``|tojson|forceescape``.
|
||
|
"""
|
||
|
rv = dumps(obj, **kwargs) \
|
||
|
.replace(u'<', u'\\u003c') \
|
||
|
.replace(u'>', u'\\u003e') \
|
||
|
.replace(u'&', u'\\u0026') \
|
||
|
.replace(u"'", u'\\u0027')
|
||
|
if not _slash_escape:
|
||
|
rv = rv.replace('\\/', '/')
|
||
|
return rv
|
||
|
|
||
|
|
||
|
def htmlsafe_dump(obj, fp, **kwargs):
|
||
|
"""Like :func:`htmlsafe_dumps` but writes into a file object."""
|
||
|
fp.write(text_type(htmlsafe_dumps(obj, **kwargs)))
|
||
|
|
||
|
|
||
|
def jsonify(*args, **kwargs):
|
||
|
"""This function wraps :func:`dumps` to add a few enhancements that make
|
||
|
life easier. It turns the JSON output into a :class:`~flask.Response`
|
||
|
object with the :mimetype:`application/json` mimetype. For convenience, it
|
||
|
also converts multiple arguments into an array or multiple keyword arguments
|
||
|
into a dict. This means that both ``jsonify(1,2,3)`` and
|
||
|
``jsonify([1,2,3])`` serialize to ``[1,2,3]``.
|
||
|
|
||
|
For clarity, the JSON serialization behavior has the following differences
|
||
|
from :func:`dumps`:
|
||
|
|
||
|
1. Single argument: Passed straight through to :func:`dumps`.
|
||
|
2. Multiple arguments: Converted to an array before being passed to
|
||
|
:func:`dumps`.
|
||
|
3. Multiple keyword arguments: Converted to a dict before being passed to
|
||
|
:func:`dumps`.
|
||
|
4. Both args and kwargs: Behavior undefined and will throw an exception.
|
||
|
|
||
|
Example usage::
|
||
|
|
||
|
from flask import jsonify
|
||
|
|
||
|
@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
|
||
|
}
|
||
|
|
||
|
|
||
|
.. versionchanged:: 0.11
|
||
|
Added support for serializing top-level arrays. This introduces a
|
||
|
security risk in ancient browsers. See :ref:`json-security` for details.
|
||
|
|
||
|
This function's response will be pretty printed if the
|
||
|
``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to True or the
|
||
|
Flask app is running in debug mode. Compressed (not pretty) formatting
|
||
|
currently means no indents and no spaces after separators.
|
||
|
|
||
|
.. versionadded:: 0.2
|
||
|
"""
|
||
|
|
||
|
indent = None
|
||
|
separators = (',', ':')
|
||
|
|
||
|
if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] or current_app.debug:
|
||
|
indent = 2
|
||
|
separators = (', ', ': ')
|
||
|
|
||
|
if args and kwargs:
|
||
|
raise TypeError('jsonify() behavior undefined when passed both args and kwargs')
|
||
|
elif len(args) == 1: # single args are passed directly to dumps()
|
||
|
data = args[0]
|
||
|
else:
|
||
|
data = args or kwargs
|
||
|
|
||
|
return current_app.response_class(
|
||
|
dumps(data, indent=indent, separators=separators) + '\n',
|
||
|
mimetype=current_app.config['JSONIFY_MIMETYPE']
|
||
|
)
|
||
|
|
||
|
|
||
|
def tojson_filter(obj, **kwargs):
|
||
|
return Markup(htmlsafe_dumps(obj, **kwargs))
|