remove simplejson

- remove encoding detection backport, json.loads supports it directly
- use str.translate instead of multiple str.replace
This commit is contained in:
David Lord 2020-04-07 07:00:15 -07:00
parent 024f0d384c
commit c43edfc7c0
No known key found for this signature in database
GPG key ID: 7A1C87E3F5BC42A8
6 changed files with 27 additions and 113 deletions

View file

@ -19,6 +19,9 @@ Unreleased
200 OK and an empty file. :issue:`3358` 200 OK and an empty file. :issue:`3358`
- When using ad-hoc certificates, check for the cryptography library - When using ad-hoc certificates, check for the cryptography library
instead of PyOpenSSL. :pr:`3492` instead of PyOpenSSL. :pr:`3492`
- JSON support no longer uses simplejson if it's installed. To use
another JSON module, override ``app.json_encoder`` and
``json_decoder``. :issue:`3555`
Version 1.1.2 Version 1.1.2

View file

@ -284,22 +284,9 @@ JSON Support
.. module:: flask.json .. module:: flask.json
Flask uses ``simplejson`` for the JSON implementation. Since simplejson Flask uses the built-in :mod:`json` module for the JSON implementation.
is provided by both the standard library as well as extension, Flask will It will delegate access to the current application's JSON encoders and
try simplejson first and then fall back to the stdlib json module. On top decoders for easier customization.
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 For usage examples, read the :mod:`json` documentation in the standard
library. The following extensions are by default applied to the stdlib's library. The following extensions are by default applied to the stdlib's

View file

@ -39,16 +39,12 @@ These distributions will not be installed automatically. Flask will detect and
use them if you install them. use them if you install them.
* `Blinker`_ provides support for :doc:`signals`. * `Blinker`_ provides support for :doc:`signals`.
* `SimpleJSON`_ is a fast JSON implementation that is compatible with
Python's ``json`` module. It is preferred for JSON operations if it is
installed.
* `python-dotenv`_ enables support for :ref:`dotenv` when running ``flask`` * `python-dotenv`_ enables support for :ref:`dotenv` when running ``flask``
commands. commands.
* `Watchdog`_ provides a faster, more efficient reloader for the development * `Watchdog`_ provides a faster, more efficient reloader for the development
server. server.
.. _Blinker: https://pythonhosted.org/blinker/ .. _Blinker: https://pythonhosted.org/blinker/
.. _SimpleJSON: https://simplejson.readthedocs.io/
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
.. _watchdog: https://pythonhosted.org/watchdog/ .. _watchdog: https://pythonhosted.org/watchdog/

View file

@ -1,10 +1,9 @@
import codecs
import io import io
import json as _json
import uuid import uuid
from datetime import date from datetime import date
from datetime import datetime from datetime import datetime
from itsdangerous import json as _json
from jinja2 import Markup from jinja2 import Markup
from werkzeug.http import http_date from werkzeug.http import http_date
@ -17,10 +16,6 @@ except ImportError:
# Python < 3.7 # Python < 3.7
dataclasses = None dataclasses = None
# Figure out if simplejson escapes slashes. This behavior was changed
# from one version to another without reason.
_slash_escape = "\\/" not in _json.dumps("/")
__all__ = [ __all__ = [
"dump", "dump",
@ -93,7 +88,7 @@ class JSONEncoder(_json.JSONEncoder):
class JSONDecoder(_json.JSONDecoder): class JSONDecoder(_json.JSONDecoder):
"""The default JSON decoder. This one does not change the behavior from """The default JSON decoder. This one does not change the behavior from
the default simplejson decoder. Consult the :mod:`json` documentation the default decoder. Consult the :mod:`json` documentation
for more information. This decoder is not only used for the load for more information. This decoder is not only used for the load
functions of this module but also :attr:`~flask.Request`. functions of this module but also :attr:`~flask.Request`.
""" """
@ -133,49 +128,6 @@ def _load_arg_defaults(kwargs, app=None):
kwargs.setdefault("cls", JSONDecoder) 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, app=None, **kwargs): def dumps(obj, app=None, **kwargs):
"""Serialize ``obj`` to a JSON-formatted string. If there is an """Serialize ``obj`` to a JSON-formatted string. If there is an
app context pushed, use the current app's configured encoder app context pushed, use the current app's configured encoder
@ -183,8 +135,7 @@ def dumps(obj, app=None, **kwargs):
:class:`JSONEncoder`. :class:`JSONEncoder`.
Takes the same arguments as the built-in :func:`json.dumps`, and Takes the same arguments as the built-in :func:`json.dumps`, and
does some extra configuration based on the application. If the does some extra configuration based on the application.
simplejson package is installed, it is preferred.
:param obj: Object to serialize to JSON. :param obj: Object to serialize to JSON.
:param app: App instance to use to configure the JSON encoder. :param app: App instance to use to configure the JSON encoder.
@ -200,8 +151,10 @@ def dumps(obj, app=None, **kwargs):
_dump_arg_defaults(kwargs, app=app) _dump_arg_defaults(kwargs, app=app)
encoding = kwargs.pop("encoding", None) encoding = kwargs.pop("encoding", None)
rv = _json.dumps(obj, **kwargs) rv = _json.dumps(obj, **kwargs)
if encoding is not None and isinstance(rv, str): if encoding is not None and isinstance(rv, str):
rv = rv.encode(encoding) rv = rv.encode(encoding)
return rv return rv
@ -209,8 +162,10 @@ def dump(obj, fp, app=None, **kwargs):
"""Like :func:`dumps` but writes into a file object.""" """Like :func:`dumps` but writes into a file object."""
_dump_arg_defaults(kwargs, app=app) _dump_arg_defaults(kwargs, app=app)
encoding = kwargs.pop("encoding", None) encoding = kwargs.pop("encoding", None)
if encoding is not None: if encoding is not None:
fp = _wrap_writer_for_text(fp, encoding) fp = _wrap_writer_for_text(fp, encoding)
_json.dump(obj, fp, **kwargs) _json.dump(obj, fp, **kwargs)
@ -221,8 +176,7 @@ def loads(s, app=None, **kwargs):
default :class:`JSONDecoder`. default :class:`JSONDecoder`.
Takes the same arguments as the built-in :func:`json.loads`, and Takes the same arguments as the built-in :func:`json.loads`, and
does some extra configuration based on the application. If the does some extra configuration based on the application.
simplejson package is installed, it is preferred.
:param s: JSON string to deserialize. :param s: JSON string to deserialize.
:param app: App instance to use to configure the JSON decoder. :param app: App instance to use to configure the JSON decoder.
@ -236,21 +190,27 @@ def loads(s, app=None, **kwargs):
context for configuration. context for configuration.
""" """
_load_arg_defaults(kwargs, app=app) _load_arg_defaults(kwargs, app=app)
if isinstance(s, bytes): encoding = kwargs.pop("encoding", None)
encoding = kwargs.pop("encoding", None)
if encoding is None: if encoding is not None and isinstance(s, bytes):
encoding = detect_encoding(s)
s = s.decode(encoding) s = s.decode(encoding)
return _json.loads(s, **kwargs) return _json.loads(s, **kwargs)
def load(fp, app=None, **kwargs): def load(fp, app=None, **kwargs):
"""Like :func:`loads` but reads from a file object.""" """Like :func:`loads` but reads from a file object."""
_load_arg_defaults(kwargs, app=app) _load_arg_defaults(kwargs, app=app)
fp = _wrap_reader_for_text(fp, kwargs.pop("encoding", None) or "utf-8") encoding = kwargs.pop("encoding", None)
fp = _wrap_reader_for_text(fp, encoding or "utf-8")
return _json.load(fp, **kwargs) return _json.load(fp, **kwargs)
_htmlsafe_map = str.maketrans(
{"<": "\\u003c", ">": "\\u003e", "&": "\\u0026", "'": "\\u0027"}
)
def htmlsafe_dumps(obj, **kwargs): def htmlsafe_dumps(obj, **kwargs):
"""Works exactly like :func:`dumps` but is safe for use in ``<script>`` """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 tags. It accepts the same arguments and returns a JSON string. Note that
@ -276,16 +236,7 @@ def htmlsafe_dumps(obj, **kwargs):
quoted. Always single quote attributes if you use the ``|tojson`` quoted. Always single quote attributes if you use the ``|tojson``
filter. Alternatively use ``|tojson|forceescape``. filter. Alternatively use ``|tojson|forceescape``.
""" """
rv = ( return dumps(obj, **kwargs).translate(_htmlsafe_map)
dumps(obj, **kwargs)
.replace("<", "\\u003c")
.replace(">", "\\u003e")
.replace("&", "\\u0026")
.replace("'", "\\u0027")
)
if not _slash_escape:
rv = rv.replace("\\/", "/")
return rv
def htmlsafe_dump(obj, fp, **kwargs): def htmlsafe_dump(obj, fp, **kwargs):

View file

@ -13,7 +13,6 @@ from werkzeug.http import parse_cache_control_header
from werkzeug.http import parse_options_header from werkzeug.http import parse_options_header
import flask import flask
from flask import json
from flask.helpers import get_debug_flag from flask.helpers import get_debug_flag
from flask.helpers import get_env from flask.helpers import get_env
@ -64,26 +63,6 @@ class FixedOffset(datetime.tzinfo):
class TestJSON: class TestJSON:
@pytest.mark.parametrize(
"value", (1, "t", True, False, None, [], [1, 2, 3], {}, {"foo": "🐍"})
)
@pytest.mark.parametrize(
"encoding",
(
"utf-8",
"utf-8-sig",
"utf-16-le",
"utf-16-be",
"utf-16",
"utf-32-le",
"utf-32-be",
"utf-32",
),
)
def test_detect_encoding(self, value, encoding):
data = json.dumps(value).encode(encoding)
assert json.loads(data) == value
@pytest.mark.parametrize("debug", (True, False)) @pytest.mark.parametrize("debug", (True, False))
def test_bad_request_debug_message(self, app, client, debug): def test_bad_request_debug_message(self, app, client, debug):
app.config["DEBUG"] = debug app.config["DEBUG"] = debug

View file

@ -1,7 +1,7 @@
[tox] [tox]
envlist = envlist =
py{38,37,36,py3} py{38,37,36,py3}
py38-{simplejson,devel,lowest} py38-{devel,lowest}
style style
docs docs
skip_missing_interpreters = true skip_missing_interpreters = true
@ -24,8 +24,6 @@ deps =
devel: https://github.com/pallets/itsdangerous/archive/master.tar.gz devel: https://github.com/pallets/itsdangerous/archive/master.tar.gz
devel: https://github.com/pallets/click/archive/master.tar.gz devel: https://github.com/pallets/click/archive/master.tar.gz
simplejson: simplejson
commands = commands =
pip install -q -e examples/tutorial[test] pip install -q -e examples/tutorial[test]
pip install -q -e examples/javascript[test] pip install -q -e examples/javascript[test]