forked from orbit-oss/flask
add json provider interface
This commit is contained in:
parent
c356c6da5f
commit
69f9845ef2
13 changed files with 662 additions and 282 deletions
15
CHANGES.rst
15
CHANGES.rst
|
|
@ -32,7 +32,22 @@ Unreleased
|
|||
``Flask.aborter_class`` and ``Flask.make_aborter`` can be used
|
||||
to customize this aborter. :issue:`4567`
|
||||
- ``flask.redirect`` will call ``app.redirect``. :issue:`4569`
|
||||
- ``flask.json`` is an instance of ``JSONProvider``. A different
|
||||
provider can be set to use a different JSON library.
|
||||
``flask.jsonify`` will call ``app.json.response``, other
|
||||
functions in ``flask.json`` will call corresponding functions in
|
||||
``app.json``. :pr:`4688`
|
||||
|
||||
- JSON configuration is moved to attributes on the default
|
||||
``app.json`` provider. ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``,
|
||||
``JSONIFY_MIMETYPE``, and ``JSONIFY_PRETTYPRINT_REGULAR`` are
|
||||
deprecated. :pr:`4688`
|
||||
- Setting custom ``json_encoder`` and ``json_decoder`` classes on the
|
||||
app or a blueprint, and the corresponding ``json.JSONEncoder`` and
|
||||
``JSONDecoder`` classes, are deprecated. JSON behavior can now be
|
||||
overridden using the ``app.json`` provider interface. :pr:`4688`
|
||||
- ``json.htmlsafe_dumps`` and ``json.htmlsafe_dump`` are deprecated,
|
||||
the function is built-in to Jinja now. :pr:`4688`
|
||||
- Refactor ``register_error_handler`` to consolidate error checking.
|
||||
Rewrite some error messages to be more consistent. :issue:`4559`
|
||||
- Use Blueprint decorators and functions intended for setup after
|
||||
|
|
|
|||
30
docs/api.rst
30
docs/api.rst
|
|
@ -236,21 +236,15 @@ JSON Support
|
|||
|
||||
.. module:: flask.json
|
||||
|
||||
Flask uses the built-in :mod:`json` module for handling JSON. It will
|
||||
use the current blueprint's or application's JSON encoder and decoder
|
||||
for easier customization. By default it handles some extra data types:
|
||||
Flask uses Python's built-in :mod:`json` module for handling JSON by
|
||||
default. The JSON implementation can be changed by assigning a different
|
||||
provider to :attr:`flask.Flask.json_provider_class` or
|
||||
:attr:`flask.Flask.json`. The functions provided by ``flask.json`` will
|
||||
use methods on ``app.json`` if an app context is active.
|
||||
|
||||
- :class:`datetime.datetime` and :class:`datetime.date` are serialized
|
||||
to :rfc:`822` strings. This is the same as the HTTP date format.
|
||||
- :class:`uuid.UUID` is serialized to a string.
|
||||
- :class:`dataclasses.dataclass` is passed to
|
||||
:func:`dataclasses.asdict`.
|
||||
- :class:`~markupsafe.Markup` (or any object with a ``__html__``
|
||||
method) will call the ``__html__`` method to get a string.
|
||||
|
||||
Jinja's ``|tojson`` filter is configured to use Flask's :func:`dumps`
|
||||
function. The filter marks the output with ``|safe`` automatically. Use
|
||||
the filter to render data inside ``<script>`` tags.
|
||||
Jinja's ``|tojson`` filter is configured to use the app's JSON provider.
|
||||
The filter marks the output with ``|safe``. Use it to render data inside
|
||||
HTML ``<script>`` tags.
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
|
|
@ -269,6 +263,14 @@ the filter to render data inside ``<script>`` tags.
|
|||
|
||||
.. autofunction:: load
|
||||
|
||||
.. autoclass:: flask.json.provider.JSONProvider
|
||||
:members:
|
||||
:member-order: bysource
|
||||
|
||||
.. autoclass:: flask.json.provider.DefaultJSONProvider
|
||||
:members:
|
||||
:member-order: bysource
|
||||
|
||||
.. autoclass:: JSONEncoder
|
||||
:members:
|
||||
|
||||
|
|
|
|||
|
|
@ -301,6 +301,10 @@ The following configuration values are used internally by Flask:
|
|||
|
||||
Default: ``True``
|
||||
|
||||
.. deprecated:: 2.2
|
||||
Will be removed in Flask 2.3. Set ``app.json.ensure_ascii``
|
||||
instead.
|
||||
|
||||
.. py:data:: JSON_SORT_KEYS
|
||||
|
||||
Sort the keys of JSON objects alphabetically. This is useful for caching
|
||||
|
|
@ -310,19 +314,30 @@ The following configuration values are used internally by Flask:
|
|||
|
||||
Default: ``True``
|
||||
|
||||
.. deprecated:: 2.2
|
||||
Will be removed in Flask 2.3. Set ``app.json.sort_keys``
|
||||
instead.
|
||||
|
||||
.. py:data:: JSONIFY_PRETTYPRINT_REGULAR
|
||||
|
||||
``jsonify`` responses will be output with newlines, spaces, and indentation
|
||||
for easier reading by humans. Always enabled in debug mode.
|
||||
:func:`~flask.jsonify` responses will be output with newlines,
|
||||
spaces, and indentation for easier reading by humans. Always enabled
|
||||
in debug mode.
|
||||
|
||||
Default: ``False``
|
||||
|
||||
.. deprecated:: 2.2
|
||||
Will be removed in Flask 2.3. Set ``app.json.compact`` instead.
|
||||
|
||||
.. py:data:: JSONIFY_MIMETYPE
|
||||
|
||||
The mimetype of ``jsonify`` responses.
|
||||
|
||||
Default: ``'application/json'``
|
||||
|
||||
.. deprecated:: 2.2
|
||||
Will be removed in Flask 2.3. Set ``app.json.mimetype`` instead.
|
||||
|
||||
.. py:data:: TEMPLATES_AUTO_RELOAD
|
||||
|
||||
Reload templates when they are changed. If not set, it will be enabled in
|
||||
|
|
@ -387,6 +402,12 @@ The following configuration values are used internally by Flask:
|
|||
.. versionchanged:: 2.2
|
||||
Removed ``PRESERVE_CONTEXT_ON_EXCEPTION``.
|
||||
|
||||
.. versionchanged:: 2.2
|
||||
``JSON_AS_ASCII``, ``JSON_SORT_KEYS``,
|
||||
``JSONIFY_MIMETYPE``, and ``JSONIFY_PRETTYPRINT_REGULAR`` will be
|
||||
removed in Flask 2.3. The default ``app.json`` provider has
|
||||
equivalent attributes instead.
|
||||
|
||||
|
||||
Configuring from Python Files
|
||||
-----------------------------
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ from werkzeug.utils import redirect as _wz_redirect
|
|||
from werkzeug.wrappers import Response as BaseResponse
|
||||
|
||||
from . import cli
|
||||
from . import json
|
||||
from . import typing as ft
|
||||
from .config import Config
|
||||
from .config import ConfigAttribute
|
||||
|
|
@ -50,7 +49,8 @@ from .helpers import get_env
|
|||
from .helpers import get_flashed_messages
|
||||
from .helpers import get_load_dotenv
|
||||
from .helpers import locked_cached_property
|
||||
from .json import jsonify
|
||||
from .json.provider import DefaultJSONProvider
|
||||
from .json.provider import JSONProvider
|
||||
from .logging import create_logger
|
||||
from .scaffold import _endpoint_from_view_func
|
||||
from .scaffold import _sentinel
|
||||
|
|
@ -315,15 +315,37 @@ class Flask(Scaffold):
|
|||
#: ``USE_X_SENDFILE`` configuration key. Defaults to ``False``.
|
||||
use_x_sendfile = ConfigAttribute("USE_X_SENDFILE")
|
||||
|
||||
#: The JSON encoder class to use. Defaults to :class:`~flask.json.JSONEncoder`.
|
||||
#: The JSON encoder class to use. Defaults to
|
||||
#: :class:`~flask.json.JSONEncoder`.
|
||||
#:
|
||||
#: .. deprecated:: 2.2
|
||||
#: Will be removed in Flask 2.3. Customize
|
||||
#: :attr:`json_provider_class` instead.
|
||||
#:
|
||||
#: .. versionadded:: 0.10
|
||||
json_encoder = json.JSONEncoder
|
||||
json_encoder: None = None
|
||||
|
||||
#: The JSON decoder class to use. Defaults to :class:`~flask.json.JSONDecoder`.
|
||||
#: The JSON decoder class to use. Defaults to
|
||||
#: :class:`~flask.json.JSONDecoder`.
|
||||
#:
|
||||
#: .. deprecated:: 2.2
|
||||
#: Will be removed in Flask 2.3. Customize
|
||||
#: :attr:`json_provider_class` instead.
|
||||
#:
|
||||
#: .. versionadded:: 0.10
|
||||
json_decoder = json.JSONDecoder
|
||||
json_decoder: None = None
|
||||
|
||||
json_provider_class: t.Type[JSONProvider] = DefaultJSONProvider
|
||||
"""A subclass of :class:`~flask.json.provider.JSONProvider`. An
|
||||
instance is created and assigned to :attr:`app.json` when creating
|
||||
the app.
|
||||
|
||||
The default, :class:`~flask.json.provider.DefaultJSONProvider`, uses
|
||||
Python's built-in :mod:`json` library. A different provider can use
|
||||
a different JSON library.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
"""
|
||||
|
||||
#: Options that are passed to the Jinja environment in
|
||||
#: :meth:`create_jinja_environment`. Changing these options after
|
||||
|
|
@ -361,10 +383,10 @@ class Flask(Scaffold):
|
|||
"TRAP_HTTP_EXCEPTIONS": False,
|
||||
"EXPLAIN_TEMPLATE_LOADING": False,
|
||||
"PREFERRED_URL_SCHEME": "http",
|
||||
"JSON_AS_ASCII": True,
|
||||
"JSON_SORT_KEYS": True,
|
||||
"JSONIFY_PRETTYPRINT_REGULAR": False,
|
||||
"JSONIFY_MIMETYPE": "application/json",
|
||||
"JSON_AS_ASCII": None,
|
||||
"JSON_SORT_KEYS": None,
|
||||
"JSONIFY_PRETTYPRINT_REGULAR": None,
|
||||
"JSONIFY_MIMETYPE": None,
|
||||
"TEMPLATES_AUTO_RELOAD": None,
|
||||
"MAX_COOKIE_SIZE": 4093,
|
||||
}
|
||||
|
|
@ -449,6 +471,22 @@ class Flask(Scaffold):
|
|||
#: Moved from ``flask.abort``, which calls this object.
|
||||
self.aborter = self.make_aborter()
|
||||
|
||||
self.json: JSONProvider = self.json_provider_class(self)
|
||||
"""Provides access to JSON methods. Functions in ``flask.json``
|
||||
will call methods on this provider when the application context
|
||||
is active. Used for handling JSON requests and responses.
|
||||
|
||||
An instance of :attr:`json_provider_class`. Can be customized by
|
||||
changing that attribute on a subclass, or by assigning to this
|
||||
attribute afterwards.
|
||||
|
||||
The default, :class:`~flask.json.provider.DefaultJSONProvider`,
|
||||
uses Python's built-in :mod:`json` library. A different provider
|
||||
can use a different JSON library.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
"""
|
||||
|
||||
#: A list of functions that are called by
|
||||
#: :meth:`handle_url_build_error` when :meth:`.url_for` raises a
|
||||
#: :exc:`~werkzeug.routing.BuildError`. Each function is called
|
||||
|
|
@ -745,7 +783,7 @@ class Flask(Scaffold):
|
|||
session=session,
|
||||
g=g,
|
||||
)
|
||||
rv.policies["json.dumps_function"] = json.dumps
|
||||
rv.policies["json.dumps_function"] = self.json.dumps
|
||||
return rv
|
||||
|
||||
def create_global_jinja_loader(self) -> DispatchingJinjaLoader:
|
||||
|
|
@ -1926,7 +1964,7 @@ class Flask(Scaffold):
|
|||
)
|
||||
status = headers = None
|
||||
elif isinstance(rv, (dict, list)):
|
||||
rv = jsonify(rv)
|
||||
rv = self.json.response(rv)
|
||||
elif isinstance(rv, BaseResponse) or callable(rv):
|
||||
# evaluate a WSGI callable, or coerce a different response
|
||||
# class to the correct type
|
||||
|
|
|
|||
|
|
@ -174,10 +174,16 @@ class Blueprint(Scaffold):
|
|||
|
||||
#: Blueprint local JSON encoder class to use. Set to ``None`` to use
|
||||
#: the app's :class:`~flask.Flask.json_encoder`.
|
||||
json_encoder = None
|
||||
#:
|
||||
#: .. deprecated:: 2.2
|
||||
#: Will be removed in Flask 2.3.
|
||||
json_encoder: None = None
|
||||
#: Blueprint local JSON decoder class to use. Set to ``None`` to use
|
||||
#: the app's :class:`~flask.Flask.json_decoder`.
|
||||
json_decoder = None
|
||||
#:
|
||||
#: .. deprecated:: 2.2
|
||||
#: Will be removed in Flask 2.3.
|
||||
json_decoder: None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -307,6 +307,7 @@ class RequestContext:
|
|||
self.app = app
|
||||
if request is None:
|
||||
request = app.request_class(environ)
|
||||
request.json_module = app.json # type: ignore[misc]
|
||||
self.request: Request = request
|
||||
self.url_adapter = None
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
import dataclasses
|
||||
import decimal
|
||||
from __future__ import annotations
|
||||
|
||||
import json as _json
|
||||
import typing as t
|
||||
import uuid
|
||||
from datetime import date
|
||||
|
||||
from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps
|
||||
from werkzeug.http import http_date
|
||||
|
||||
from ..globals import current_app
|
||||
from ..globals import request
|
||||
from .provider import _default
|
||||
|
||||
if t.TYPE_CHECKING: # pragma: no cover
|
||||
from ..app import Flask
|
||||
|
|
@ -31,23 +28,30 @@ class JSONEncoder(_json.JSONEncoder):
|
|||
|
||||
Assign a subclass of this to :attr:`flask.Flask.json_encoder` or
|
||||
:attr:`flask.Blueprint.json_encoder` to override the default.
|
||||
|
||||
.. deprecated:: 2.2
|
||||
Will be removed in Flask 2.3. Use ``app.json`` instead.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"'JSONEncoder' is deprecated and will be removed in"
|
||||
" Flask 2.3. Use 'Flask.json' to provide an alternate"
|
||||
" JSON implementation instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def default(self, o: t.Any) -> t.Any:
|
||||
"""Convert ``o`` to a JSON serializable type. See
|
||||
:meth:`json.JSONEncoder.default`. Python does not support
|
||||
overriding how basic types like ``str`` or ``list`` are
|
||||
serialized, they are handled before this method.
|
||||
"""
|
||||
if isinstance(o, date):
|
||||
return http_date(o)
|
||||
if isinstance(o, (decimal.Decimal, uuid.UUID)):
|
||||
return str(o)
|
||||
if dataclasses and dataclasses.is_dataclass(o):
|
||||
return dataclasses.asdict(o)
|
||||
if hasattr(o, "__html__"):
|
||||
return str(o.__html__())
|
||||
return super().default(o)
|
||||
return _default(o)
|
||||
|
||||
|
||||
class JSONDecoder(_json.JSONDecoder):
|
||||
|
|
@ -58,144 +62,193 @@ class JSONDecoder(_json.JSONDecoder):
|
|||
|
||||
Assign a subclass of this to :attr:`flask.Flask.json_decoder` or
|
||||
:attr:`flask.Blueprint.json_decoder` to override the default.
|
||||
|
||||
.. deprecated:: 2.2
|
||||
Will be removed in Flask 2.3. Use ``app.json`` instead.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
import warnings
|
||||
|
||||
def _dump_arg_defaults(
|
||||
kwargs: t.Dict[str, t.Any], app: t.Optional["Flask"] = None
|
||||
) -> None:
|
||||
"""Inject default arguments for dump functions."""
|
||||
if app is None:
|
||||
app = current_app
|
||||
|
||||
if app:
|
||||
cls = app.json_encoder
|
||||
bp = app.blueprints.get(request.blueprint) if request else None # type: ignore
|
||||
if bp is not None and bp.json_encoder is not None:
|
||||
cls = bp.json_encoder
|
||||
|
||||
# Only set a custom encoder if it has custom behavior. This is
|
||||
# faster on PyPy.
|
||||
if cls is not _json.JSONEncoder:
|
||||
kwargs.setdefault("cls", cls)
|
||||
|
||||
kwargs.setdefault("cls", cls)
|
||||
kwargs.setdefault("ensure_ascii", app.config["JSON_AS_ASCII"])
|
||||
kwargs.setdefault("sort_keys", app.config["JSON_SORT_KEYS"])
|
||||
else:
|
||||
kwargs.setdefault("sort_keys", True)
|
||||
kwargs.setdefault("cls", JSONEncoder)
|
||||
warnings.warn(
|
||||
"'JSONDecoder' is deprecated and will be removed in"
|
||||
" Flask 2.3. Use 'Flask.json' to provide an alternate"
|
||||
" JSON implementation instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
def _load_arg_defaults(
|
||||
kwargs: t.Dict[str, t.Any], app: t.Optional["Flask"] = None
|
||||
) -> None:
|
||||
"""Inject default arguments for load functions."""
|
||||
if app is None:
|
||||
app = current_app
|
||||
def dumps(obj: t.Any, *, app: Flask | None = None, **kwargs: t.Any) -> str:
|
||||
"""Serialize data as JSON.
|
||||
|
||||
if app:
|
||||
cls = app.json_decoder
|
||||
bp = app.blueprints.get(request.blueprint) if request else None # type: ignore
|
||||
if bp is not None and bp.json_decoder is not None:
|
||||
cls = bp.json_decoder
|
||||
If :data:`~flask.current_app` is available, it will use its
|
||||
:meth:`app.json.dumps() <flask.json.provider.JSONProvider.dumps>`
|
||||
method, otherwise it will use :func:`json.dumps`.
|
||||
|
||||
# Only set a custom decoder if it has custom behavior. This is
|
||||
# faster on PyPy.
|
||||
if cls not in {JSONDecoder, _json.JSONDecoder}:
|
||||
kwargs.setdefault("cls", cls)
|
||||
:param obj: The data to serialize.
|
||||
:param kwargs: Arguments passed to the ``dumps`` implementation.
|
||||
|
||||
.. versionchanged:: 2.2
|
||||
Calls ``current_app.json.dumps``, allowing an app to override
|
||||
the behavior.
|
||||
|
||||
def dumps(obj: t.Any, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> str:
|
||||
"""Serialize an object to a string of JSON.
|
||||
|
||||
Takes the same arguments as the built-in :func:`json.dumps`, with
|
||||
some defaults from application configuration.
|
||||
|
||||
:param obj: Object to serialize to JSON.
|
||||
:param app: Use this app's config instead of the active app context
|
||||
or defaults.
|
||||
:param kwargs: Extra arguments passed to :func:`json.dumps`.
|
||||
.. versionchanged:: 2.2
|
||||
The ``app`` parameter will be removed in Flask 2.3.
|
||||
|
||||
.. versionchanged:: 2.0.2
|
||||
:class:`decimal.Decimal` is supported by converting to a string.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
``encoding`` is deprecated and will be removed in Flask 2.1.
|
||||
``encoding`` will be removed in Flask 2.1.
|
||||
|
||||
.. versionchanged:: 1.0.3
|
||||
``app`` can be passed directly, rather than requiring an app
|
||||
context for configuration.
|
||||
"""
|
||||
_dump_arg_defaults(kwargs, app=app)
|
||||
if app is not None:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'app' parameter is deprecated and will be removed in"
|
||||
" Flask 2.3. Call 'app.json.dumps' directly instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
app = current_app
|
||||
|
||||
if app:
|
||||
return app.json.dumps(obj, **kwargs)
|
||||
|
||||
kwargs.setdefault("default", _default)
|
||||
return _json.dumps(obj, **kwargs)
|
||||
|
||||
|
||||
def dump(
|
||||
obj: t.Any, fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any
|
||||
obj: t.Any, fp: t.IO[str], *, app: Flask | None = None, **kwargs: t.Any
|
||||
) -> None:
|
||||
"""Serialize an object to JSON written to a file object.
|
||||
"""Serialize data as JSON and write to a file.
|
||||
|
||||
Takes the same arguments as the built-in :func:`json.dump`, with
|
||||
some defaults from application configuration.
|
||||
If :data:`~flask.current_app` is available, it will use its
|
||||
:meth:`app.json.dump() <flask.json.provider.JSONProvider.dump>`
|
||||
method, otherwise it will use :func:`json.dump`.
|
||||
|
||||
:param obj: Object to serialize to JSON.
|
||||
:param fp: File object to write JSON to.
|
||||
:param app: Use this app's config instead of the active app context
|
||||
or defaults.
|
||||
:param kwargs: Extra arguments passed to :func:`json.dump`.
|
||||
:param obj: The data to serialize.
|
||||
:param fp: A file opened for writing text. Should use the UTF-8
|
||||
encoding to be valid JSON.
|
||||
:param kwargs: Arguments passed to the ``dump`` implementation.
|
||||
|
||||
.. versionchanged:: 2.2
|
||||
Calls ``current_app.json.dump``, allowing an app to override
|
||||
the behavior.
|
||||
|
||||
.. versionchanged:: 2.2
|
||||
The ``app`` parameter will be removed in Flask 2.3.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Writing to a binary file, and the ``encoding`` argument, is
|
||||
deprecated and will be removed in Flask 2.1.
|
||||
Writing to a binary file, and the ``encoding`` argument, will be
|
||||
removed in Flask 2.1.
|
||||
"""
|
||||
_dump_arg_defaults(kwargs, app=app)
|
||||
_json.dump(obj, fp, **kwargs)
|
||||
if app is not None:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'app' parameter is deprecated and will be removed in"
|
||||
" Flask 2.3. Call 'app.json.dump' directly instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
app = current_app
|
||||
|
||||
if app:
|
||||
app.json.dump(obj, fp, **kwargs)
|
||||
else:
|
||||
kwargs.setdefault("default", _default)
|
||||
_json.dump(obj, fp, **kwargs)
|
||||
|
||||
|
||||
def loads(
|
||||
s: t.Union[str, bytes],
|
||||
app: t.Optional["Flask"] = None,
|
||||
**kwargs: t.Any,
|
||||
) -> t.Any:
|
||||
"""Deserialize an object from a string of JSON.
|
||||
def loads(s: str | bytes, *, app: Flask | None = None, **kwargs: t.Any) -> t.Any:
|
||||
"""Deserialize data as JSON.
|
||||
|
||||
Takes the same arguments as the built-in :func:`json.loads`, with
|
||||
some defaults from application configuration.
|
||||
If :data:`~flask.current_app` is available, it will use its
|
||||
:meth:`app.json.loads() <flask.json.provider.JSONProvider.loads>`
|
||||
method, otherwise it will use :func:`json.loads`.
|
||||
|
||||
:param s: JSON string to deserialize.
|
||||
:param app: Use this app's config instead of the active app context
|
||||
or defaults.
|
||||
:param kwargs: Extra arguments passed to :func:`json.loads`.
|
||||
:param s: Text or UTF-8 bytes.
|
||||
:param kwargs: Arguments passed to the ``loads`` implementation.
|
||||
|
||||
.. versionchanged:: 2.2
|
||||
Calls ``current_app.json.loads``, allowing an app to override
|
||||
the behavior.
|
||||
|
||||
.. versionchanged:: 2.2
|
||||
The ``app`` parameter will be removed in Flask 2.3.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
``encoding`` is deprecated and will be removed in Flask 2.1. The
|
||||
data must be a string or UTF-8 bytes.
|
||||
``encoding`` will be removed in Flask 2.1. The data must be a
|
||||
string or UTF-8 bytes.
|
||||
|
||||
.. versionchanged:: 1.0.3
|
||||
``app`` can be passed directly, rather than requiring an app
|
||||
context for configuration.
|
||||
"""
|
||||
_load_arg_defaults(kwargs, app=app)
|
||||
if app is not None:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'app' parameter is deprecated and will be removed in"
|
||||
" Flask 2.3. Call 'app.json.loads' directly instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
app = current_app
|
||||
|
||||
if app:
|
||||
return app.json.loads(s, **kwargs)
|
||||
|
||||
return _json.loads(s, **kwargs)
|
||||
|
||||
|
||||
def load(fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any:
|
||||
"""Deserialize an object from JSON read from a file object.
|
||||
def load(fp: t.IO[t.AnyStr], *, app: Flask | None = None, **kwargs: t.Any) -> t.Any:
|
||||
"""Deserialize data as JSON read from a file.
|
||||
|
||||
Takes the same arguments as the built-in :func:`json.load`, with
|
||||
some defaults from application configuration.
|
||||
If :data:`~flask.current_app` is available, it will use its
|
||||
:meth:`app.json.load() <flask.json.provider.JSONProvider.load>`
|
||||
method, otherwise it will use :func:`json.load`.
|
||||
|
||||
:param fp: File object to read JSON from.
|
||||
:param app: Use this app's config instead of the active app context
|
||||
or defaults.
|
||||
:param kwargs: Extra arguments passed to :func:`json.load`.
|
||||
:param fp: A file opened for reading text or UTF-8 bytes.
|
||||
:param kwargs: Arguments passed to the ``load`` implementation.
|
||||
|
||||
.. versionchanged:: 2.2
|
||||
Calls ``current_app.json.load``, allowing an app to override
|
||||
the behavior.
|
||||
|
||||
.. versionchanged:: 2.2
|
||||
The ``app`` parameter will be removed in Flask 2.3.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
``encoding`` is deprecated and will be removed in Flask 2.1. The
|
||||
file must be text mode, or binary mode with UTF-8 bytes.
|
||||
``encoding`` will be removed in Flask 2.1. The file must be text
|
||||
mode, or binary mode with UTF-8 bytes.
|
||||
"""
|
||||
_load_arg_defaults(kwargs, app=app)
|
||||
if app is not None:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'app' parameter is deprecated and will be removed in"
|
||||
" Flask 2.3. Call 'app.json.load' directly instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
app = current_app
|
||||
|
||||
if app:
|
||||
return app.json.load(fp, **kwargs)
|
||||
|
||||
return _json.load(fp, **kwargs)
|
||||
|
||||
|
||||
|
|
@ -211,6 +264,9 @@ def htmlsafe_dumps(obj: t.Any, **kwargs: t.Any) -> str:
|
|||
double quoted; either use single quotes or the ``|forceescape``
|
||||
filter.
|
||||
|
||||
.. deprecated:: 2.2
|
||||
Will be removed in Flask 2.3. This is built-in to Jinja now.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Uses :func:`jinja2.utils.htmlsafe_json_dumps`. The returned
|
||||
value is marked safe by wrapping in :class:`~markupsafe.Markup`.
|
||||
|
|
@ -220,6 +276,14 @@ def htmlsafe_dumps(obj: t.Any, **kwargs: t.Any) -> str:
|
|||
``<script>`` tags, and single-quoted attributes without further
|
||||
escaping.
|
||||
"""
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"'htmlsafe_dumps' is deprecated and will be removed in Flask"
|
||||
" 2.3. Use 'jinja2.utils.htmlsafe_json_dumps' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return _jinja_htmlsafe_dumps(obj, dumps=dumps, **kwargs)
|
||||
|
||||
|
||||
|
|
@ -227,77 +291,51 @@ def htmlsafe_dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
|
|||
"""Serialize an object to JSON written to a file object, replacing
|
||||
HTML-unsafe characters with Unicode escapes. See
|
||||
:func:`htmlsafe_dumps` and :func:`dumps`.
|
||||
|
||||
.. deprecated:: 2.2
|
||||
Will be removed in Flask 2.3.
|
||||
"""
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"'htmlsafe_dump' is deprecated and will be removed in Flask"
|
||||
" 2.3. Use 'jinja2.utils.htmlsafe_json_dumps' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
fp.write(htmlsafe_dumps(obj, **kwargs))
|
||||
|
||||
|
||||
def jsonify(*args: t.Any, **kwargs: t.Any) -> "Response":
|
||||
"""Serialize data to JSON and wrap it in a :class:`~flask.Response`
|
||||
with the :mimetype:`application/json` mimetype.
|
||||
def jsonify(*args: t.Any, **kwargs: t.Any) -> Response:
|
||||
"""Serialize the given arguments as JSON, and return a
|
||||
:class:`~flask.Response` object with the ``application/json``
|
||||
mimetype. A dict or list returned from a view will be converted to a
|
||||
JSON response automatically without needing to call this.
|
||||
|
||||
Uses :func:`dumps` to serialize the data, but ``args`` and
|
||||
``kwargs`` are treated as data rather than arguments to
|
||||
:func:`json.dumps`.
|
||||
This requires an active request or application context, and calls
|
||||
:meth:`app.json.response() <flask.json.provider.JSONProvider.response>`.
|
||||
|
||||
1. Single argument: Treated as a single value.
|
||||
2. Multiple arguments: Treated as a list of values.
|
||||
``jsonify(1, 2, 3)`` is the same as ``jsonify([1, 2, 3])``.
|
||||
3. Keyword arguments: Treated as a dict of values.
|
||||
``jsonify(data=data, errors=errors)`` is the same as
|
||||
``jsonify({"data": data, "errors": errors})``.
|
||||
4. Passing both arguments and keyword arguments is not allowed as
|
||||
it's not clear what should happen.
|
||||
In debug mode, the output is formatted with indentation to make it
|
||||
easier to read. This may also be controlled by the provider.
|
||||
|
||||
.. code-block:: python
|
||||
Either positional or keyword arguments can be given, not both.
|
||||
If no arguments are given, ``None`` is serialized.
|
||||
|
||||
from flask import jsonify
|
||||
:param args: A single value to serialize, or multiple values to
|
||||
treat as a list to serialize.
|
||||
:param kwargs: Treat as a dict to serialize.
|
||||
|
||||
@app.route("/users/me")
|
||||
def get_current_user():
|
||||
return jsonify(
|
||||
username=g.user.username,
|
||||
email=g.user.email,
|
||||
id=g.user.id,
|
||||
)
|
||||
|
||||
Will return a JSON response like this:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"username": "admin",
|
||||
"email": "admin@localhost",
|
||||
"id": 42
|
||||
}
|
||||
|
||||
The default output omits indents and spaces after separators. In
|
||||
debug mode or if :data:`JSONIFY_PRETTYPRINT_REGULAR` is ``True``,
|
||||
the output will be formatted to be easier to read.
|
||||
.. versionchanged:: 2.2
|
||||
Calls ``current_app.json.response``, allowing an app to override
|
||||
the behavior.
|
||||
|
||||
.. versionchanged:: 2.0.2
|
||||
:class:`decimal.Decimal` is supported by converting to a string.
|
||||
|
||||
.. versionchanged:: 0.11
|
||||
Added support for serializing top-level arrays. This introduces
|
||||
a security risk in ancient browsers. See :ref:`security-json`.
|
||||
Added support for serializing top-level arrays. This was a
|
||||
security risk in ancient browsers. See :ref:`security-json`.
|
||||
|
||||
.. 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(
|
||||
f"{dumps(data, indent=indent, separators=separators)}\n",
|
||||
mimetype=current_app.config["JSONIFY_MIMETYPE"],
|
||||
)
|
||||
return current_app.json.response(*args, **kwargs)
|
||||
|
|
|
|||
310
src/flask/json/provider.py
Normal file
310
src/flask/json/provider.py
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import decimal
|
||||
import json
|
||||
import typing as t
|
||||
import uuid
|
||||
import weakref
|
||||
from datetime import date
|
||||
|
||||
from werkzeug.http import http_date
|
||||
|
||||
from ..globals import request
|
||||
|
||||
if t.TYPE_CHECKING: # pragma: no cover
|
||||
from ..app import Flask
|
||||
from ..wrappers import Response
|
||||
|
||||
|
||||
class JSONProvider:
|
||||
"""A standard set of JSON operations for an application. Subclasses
|
||||
of this can be used to customize JSON behavior or use different
|
||||
JSON libraries.
|
||||
|
||||
To implement a provider for a specific library, subclass this base
|
||||
class and implement at least :meth:`dumps` and :meth:`loads`. All
|
||||
other methods have default implementations.
|
||||
|
||||
To use a different provider, either subclass ``Flask`` and set
|
||||
:attr:`~flask.Flask.json_provider_class` to a provider class, or set
|
||||
:attr:`app.json <flask.Flask.json>` to an instance of the class.
|
||||
|
||||
:param app: An application instance. This will be stored as a
|
||||
:class:`weakref.proxy` on the :attr:`_app` attribute.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
"""
|
||||
|
||||
def __init__(self, app: Flask) -> None:
|
||||
self._app = weakref.proxy(app)
|
||||
|
||||
def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
|
||||
"""Serialize data as JSON.
|
||||
|
||||
:param obj: The data to serialize.
|
||||
:param kwargs: May be passed to the underlying JSON library.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def dump(self, obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
|
||||
"""Serialize data as JSON and write to a file.
|
||||
|
||||
:param obj: The data to serialize.
|
||||
:param fp: A file opened for writing text. Should use the UTF-8
|
||||
encoding to be valid JSON.
|
||||
:param kwargs: May be passed to the underlying JSON library.
|
||||
"""
|
||||
fp.write(self.dumps(obj, **kwargs))
|
||||
|
||||
def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
|
||||
"""Deserialize data as JSON.
|
||||
|
||||
:param s: Text or UTF-8 bytes.
|
||||
:param kwargs: May be passed to the underlying JSON library.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def load(self, fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any:
|
||||
"""Deserialize data as JSON read from a file.
|
||||
|
||||
:param fp: A file opened for reading text or UTF-8 bytes.
|
||||
:param kwargs: May be passed to the underlying JSON library.
|
||||
"""
|
||||
return self.loads(fp.read(), **kwargs)
|
||||
|
||||
def _prepare_response_obj(
|
||||
self, args: t.Tuple[t.Any, ...], kwargs: t.Dict[str, t.Any]
|
||||
) -> t.Any:
|
||||
if args and kwargs:
|
||||
raise TypeError("app.json.response() takes either args or kwargs, not both")
|
||||
|
||||
if not args and not kwargs:
|
||||
return None
|
||||
|
||||
if len(args) == 1:
|
||||
return args[0]
|
||||
|
||||
return args or kwargs
|
||||
|
||||
def response(self, *args: t.Any, **kwargs: t.Any) -> Response:
|
||||
"""Serialize the given arguments as JSON, and return a
|
||||
:class:`~flask.Response` object with the ``application/json``
|
||||
mimetype.
|
||||
|
||||
The :func:`~flask.json.jsonify` function calls this method for
|
||||
the current application.
|
||||
|
||||
Either positional or keyword arguments can be given, not both.
|
||||
If no arguments are given, ``None`` is serialized.
|
||||
|
||||
:param args: A single value to serialize, or multiple values to
|
||||
treat as a list to serialize.
|
||||
:param kwargs: Treat as a dict to serialize.
|
||||
"""
|
||||
obj = self._prepare_response_obj(args, kwargs)
|
||||
return self._app.response_class(self.dumps(obj), mimetype="application/json")
|
||||
|
||||
|
||||
def _default(o: t.Any) -> t.Any:
|
||||
if isinstance(o, date):
|
||||
return http_date(o)
|
||||
|
||||
if isinstance(o, (decimal.Decimal, uuid.UUID)):
|
||||
return str(o)
|
||||
|
||||
if dataclasses and dataclasses.is_dataclass(o):
|
||||
return dataclasses.asdict(o)
|
||||
|
||||
if hasattr(o, "__html__"):
|
||||
return str(o.__html__())
|
||||
|
||||
raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
|
||||
|
||||
|
||||
class DefaultJSONProvider(JSONProvider):
|
||||
"""Provide JSON operations using Python's built-in :mod:`json`
|
||||
library. Serializes the following additional data types:
|
||||
|
||||
- :class:`datetime.datetime` and :class:`datetime.date` are
|
||||
serialized to :rfc:`822` strings. This is the same as the HTTP
|
||||
date format.
|
||||
- :class:`uuid.UUID` is serialized to a string.
|
||||
- :class:`dataclasses.dataclass` is passed to
|
||||
:func:`dataclasses.asdict`.
|
||||
- :class:`~markupsafe.Markup` (or any object with a ``__html__``
|
||||
method) will call the ``__html__`` method to get a string.
|
||||
"""
|
||||
|
||||
default: t.Callable[[t.Any], t.Any] = staticmethod(
|
||||
_default
|
||||
) # type: ignore[assignment]
|
||||
"""Apply this function to any object that :meth:`json.dumps` does
|
||||
not know how to serialize. It should return a valid JSON type or
|
||||
raise a ``TypeError``.
|
||||
"""
|
||||
|
||||
ensure_ascii = True
|
||||
"""Replace non-ASCII characters with escape sequences. This may be
|
||||
more compatible with some clients, but can be disabled for better
|
||||
performance and size.
|
||||
"""
|
||||
|
||||
sort_keys = True
|
||||
"""Sort the keys in any serialized dicts. This may be useful for
|
||||
some caching situations, but can be disabled for better performance.
|
||||
When enabled, keys must all be strings, they are not converted
|
||||
before sorting.
|
||||
"""
|
||||
|
||||
compact: bool | None = None
|
||||
"""If ``True``, or ``None`` out of debug mode, the :meth:`response`
|
||||
output will not add indentation, newlines, or spaces. If ``False``,
|
||||
or ``None`` in debug mode, it will use a non-compact representation.
|
||||
"""
|
||||
|
||||
mimetype = "application/json"
|
||||
"""The mimetype set in :meth:`response`."""
|
||||
|
||||
def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
|
||||
"""Serialize data as JSON to a string.
|
||||
|
||||
Keyword arguments are passed to :func:`json.dumps`. Sets some
|
||||
parameter defaults from the :attr:`default`,
|
||||
:attr:`ensure_ascii`, and :attr:`sort_keys` attributes.
|
||||
|
||||
:param obj: The data to serialize.
|
||||
:param kwargs: Passed to :func:`json.dumps`.
|
||||
"""
|
||||
cls = self._app.json_encoder
|
||||
bp = self._app.blueprints.get(request.blueprint) if request else None
|
||||
|
||||
if bp is not None and bp.json_encoder is not None:
|
||||
cls = bp.json_encoder
|
||||
|
||||
if cls is not None:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"Setting 'json_encoder' on the app or a blueprint is"
|
||||
" deprecated and will be removed in Flask 2.3."
|
||||
" Customize 'app.json' instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
kwargs.setdefault("cls", cls)
|
||||
|
||||
if "default" not in cls.__dict__:
|
||||
kwargs.setdefault("default", self.default)
|
||||
else:
|
||||
kwargs.setdefault("default", self.default)
|
||||
|
||||
ensure_ascii = self._app.config["JSON_AS_ASCII"]
|
||||
sort_keys = self._app.config["JSON_SORT_KEYS"]
|
||||
|
||||
if ensure_ascii is not None:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'JSON_AS_ASCII' config key is deprecated and will"
|
||||
" be removed in Flask 2.3. Set 'app.json.ensure_ascii'"
|
||||
" instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
else:
|
||||
ensure_ascii = self.ensure_ascii
|
||||
|
||||
if sort_keys is not None:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'JSON_SORT_KEYS' config key is deprecated and will"
|
||||
" be removed in Flask 2.3. Set 'app.json.sort_keys'"
|
||||
" instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
else:
|
||||
sort_keys = self.sort_keys
|
||||
|
||||
kwargs.setdefault("ensure_ascii", ensure_ascii)
|
||||
kwargs.setdefault("sort_keys", sort_keys)
|
||||
return json.dumps(obj, **kwargs)
|
||||
|
||||
def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
|
||||
"""Deserialize data as JSON from a string or bytes.
|
||||
|
||||
:param s: Text or UTF-8 bytes.
|
||||
:param kwargs: Passed to :func:`json.loads`.
|
||||
"""
|
||||
cls = self._app.json_decoder
|
||||
bp = self._app.blueprints.get(request.blueprint) if request else None
|
||||
|
||||
if bp is not None and bp.json_decoder is not None:
|
||||
cls = bp.json_decoder
|
||||
|
||||
if cls is not None:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"Setting 'json_decoder' on the app or a blueprint is"
|
||||
" deprecated and will be removed in Flask 2.3."
|
||||
" Customize 'app.json' instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
kwargs.setdefault("cls", cls)
|
||||
|
||||
return json.loads(s, **kwargs)
|
||||
|
||||
def response(self, *args: t.Any, **kwargs: t.Any) -> Response:
|
||||
"""Serialize the given arguments as JSON, and return a
|
||||
:class:`~flask.Response` object with it. The response mimetype
|
||||
will be "application/json" and can be changed with
|
||||
:attr:`mimetype`.
|
||||
|
||||
If :attr:`compact` is ``False`` or debug mode is enabled, the
|
||||
output will be formatted to be easier to read.
|
||||
|
||||
Either positional or keyword arguments can be given, not both.
|
||||
If no arguments are given, ``None`` is serialized.
|
||||
|
||||
:param args: A single value to serialize, or multiple values to
|
||||
treat as a list to serialize.
|
||||
:param kwargs: Treat as a dict to serialize.
|
||||
"""
|
||||
obj = self._prepare_response_obj(args, kwargs)
|
||||
dump_args: t.Dict[str, t.Any] = {}
|
||||
pretty = self._app.config["JSONIFY_PRETTYPRINT_REGULAR"]
|
||||
mimetype = self._app.config["JSONIFY_MIMETYPE"]
|
||||
|
||||
if pretty is not None:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'JSONIFY_PRETTYPRINT_REGULAR' config key is"
|
||||
" deprecated and will be removed in Flask 2.3. Set"
|
||||
" 'app.json.compact' instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
compact: bool | None = not pretty
|
||||
else:
|
||||
compact = self.compact
|
||||
|
||||
if (compact is None and self._app.debug) or compact is False:
|
||||
dump_args.setdefault("indent", 2)
|
||||
else:
|
||||
dump_args.setdefault("separators", (",", ":"))
|
||||
|
||||
if mimetype is not None:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'JSONIFY_MIMETYPE' config key is deprecated and"
|
||||
" will be removed in Flask 2.3. Set 'app.json.mimetype'"
|
||||
" instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
else:
|
||||
mimetype = self.mimetype
|
||||
|
||||
return self._app.response_class(
|
||||
f"{self.dumps(obj, **dump_args)}\n", mimetype=mimetype
|
||||
)
|
||||
|
|
@ -6,8 +6,6 @@ import sys
|
|||
import typing as t
|
||||
from collections import defaultdict
|
||||
from functools import update_wrapper
|
||||
from json import JSONDecoder
|
||||
from json import JSONEncoder
|
||||
|
||||
from jinja2 import FileSystemLoader
|
||||
from werkzeug.exceptions import default_exceptions
|
||||
|
|
@ -76,11 +74,17 @@ class Scaffold:
|
|||
|
||||
#: JSON encoder class used by :func:`flask.json.dumps`. If a
|
||||
#: blueprint sets this, it will be used instead of the app's value.
|
||||
json_encoder: t.Optional[t.Type[JSONEncoder]] = None
|
||||
#:
|
||||
#: .. deprecated:: 2.2
|
||||
#: Will be removed in Flask 2.3.
|
||||
json_encoder: None = None
|
||||
|
||||
#: JSON decoder class used by :func:`flask.json.loads`. If a
|
||||
#: blueprint sets this, it will be used instead of the app's value.
|
||||
json_decoder: t.Optional[t.Type[JSONDecoder]] = None
|
||||
#:
|
||||
#: .. deprecated:: 2.2
|
||||
#: Will be removed in Flask 2.3.
|
||||
json_decoder: None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ from werkzeug.wrappers import Request as BaseRequest
|
|||
|
||||
from .cli import ScriptInfo
|
||||
from .globals import _cv_request
|
||||
from .json import dumps as json_dumps
|
||||
from .sessions import SessionMixin
|
||||
|
||||
if t.TYPE_CHECKING: # pragma: no cover
|
||||
|
|
@ -89,8 +88,7 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
|
|||
The serialization will be configured according to the config associated
|
||||
with this EnvironBuilder's ``app``.
|
||||
"""
|
||||
kwargs.setdefault("app", self.app)
|
||||
return json_dumps(obj, **kwargs)
|
||||
return self.app.json.dumps(obj, **kwargs)
|
||||
|
||||
|
||||
class FlaskClient(Client):
|
||||
|
|
@ -227,6 +225,7 @@ class FlaskClient(Client):
|
|||
buffered=buffered,
|
||||
follow_redirects=follow_redirects,
|
||||
)
|
||||
response.json_module = self.application.json # type: ignore[misc]
|
||||
|
||||
# Re-push contexts that were preserved during the request.
|
||||
while self._new_contexts:
|
||||
|
|
|
|||
|
|
@ -1302,28 +1302,17 @@ def test_make_response_with_response_instance(app, req_ctx):
|
|||
assert rv.headers["X-Foo"] == "bar"
|
||||
|
||||
|
||||
def test_jsonify_no_prettyprint(app, req_ctx):
|
||||
app.config.update({"JSONIFY_PRETTYPRINT_REGULAR": False})
|
||||
compressed_msg = b'{"msg":{"submsg":"W00t"},"msg2":"foobar"}\n'
|
||||
uncompressed_msg = {"msg": {"submsg": "W00t"}, "msg2": "foobar"}
|
||||
|
||||
rv = flask.make_response(flask.jsonify(uncompressed_msg), 200)
|
||||
assert rv.data == compressed_msg
|
||||
|
||||
|
||||
def test_jsonify_prettyprint(app, req_ctx):
|
||||
app.config.update({"JSONIFY_PRETTYPRINT_REGULAR": True})
|
||||
compressed_msg = {"msg": {"submsg": "W00t"}, "msg2": "foobar"}
|
||||
pretty_response = (
|
||||
b'{\n "msg": {\n "submsg": "W00t"\n }, \n "msg2": "foobar"\n}\n'
|
||||
)
|
||||
|
||||
rv = flask.make_response(flask.jsonify(compressed_msg), 200)
|
||||
assert rv.data == pretty_response
|
||||
@pytest.mark.parametrize("compact", [True, False])
|
||||
def test_jsonify_no_prettyprint(app, compact):
|
||||
app.json.compact = compact
|
||||
rv = app.json.response({"msg": {"submsg": "W00t"}, "msg2": "foobar"})
|
||||
data = rv.data.strip()
|
||||
assert (b" " not in data) is compact
|
||||
assert (b"\n" not in data) is compact
|
||||
|
||||
|
||||
def test_jsonify_mimetype(app, req_ctx):
|
||||
app.config.update({"JSONIFY_MIMETYPE": "application/vnd.api+json"})
|
||||
app.json.mimetype = "application/vnd.api+json"
|
||||
msg = {"msg": {"submsg": "W00t"}}
|
||||
rv = flask.make_response(flask.jsonify(msg), 200)
|
||||
assert rv.mimetype == "application/vnd.api+json"
|
||||
|
|
@ -1333,15 +1322,15 @@ def test_json_dump_dataclass(app, req_ctx):
|
|||
from dataclasses import make_dataclass
|
||||
|
||||
Data = make_dataclass("Data", [("name", str)])
|
||||
value = flask.json.dumps(Data("Flask"), app=app)
|
||||
value = flask.json.loads(value, app=app)
|
||||
value = app.json.dumps(Data("Flask"))
|
||||
value = app.json.loads(value)
|
||||
assert value == {"name": "Flask"}
|
||||
|
||||
|
||||
def test_jsonify_args_and_kwargs_check(app, req_ctx):
|
||||
with pytest.raises(TypeError) as e:
|
||||
flask.jsonify("fake args", kwargs="fake")
|
||||
assert "behavior undefined" in str(e.value)
|
||||
assert "args or kwargs" in str(e.value)
|
||||
|
||||
|
||||
def test_url_generation(app, req_ctx):
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from werkzeug.http import http_date
|
|||
|
||||
import flask
|
||||
from flask import json
|
||||
from flask.json.provider import DefaultJSONProvider
|
||||
|
||||
|
||||
@pytest.mark.parametrize("debug", (True, False))
|
||||
|
|
@ -48,9 +49,8 @@ def test_json_custom_mimetypes(app, client):
|
|||
"test_value,expected", [(True, '"\\u2603"'), (False, '"\u2603"')]
|
||||
)
|
||||
def test_json_as_unicode(test_value, expected, app, app_ctx):
|
||||
|
||||
app.config["JSON_AS_ASCII"] = test_value
|
||||
rv = flask.json.dumps("\N{SNOWMAN}")
|
||||
app.json.ensure_ascii = test_value
|
||||
rv = app.json.dumps("\N{SNOWMAN}")
|
||||
assert rv == expected
|
||||
|
||||
|
||||
|
|
@ -170,7 +170,7 @@ def test_jsonify_aware_datetimes(tz):
|
|||
dt = datetime.datetime(2017, 1, 1, 12, 34, 56, tzinfo=tzinfo)
|
||||
gmt = FixedOffset(hours=0, name="GMT")
|
||||
expected = dt.astimezone(gmt).strftime('"%a, %d %b %Y %H:%M:%S %Z"')
|
||||
assert flask.json.JSONEncoder().encode(dt) == expected
|
||||
assert flask.json.dumps(dt) == expected
|
||||
|
||||
|
||||
def test_jsonify_uuid_types(app, client):
|
||||
|
|
@ -225,24 +225,25 @@ def test_json_customization(app, client):
|
|||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
class MyEncoder(flask.json.JSONEncoder):
|
||||
def default(self, o):
|
||||
if isinstance(o, X):
|
||||
return f"<{o.val}>"
|
||||
return flask.json.JSONEncoder.default(self, o)
|
||||
def default(o):
|
||||
if isinstance(o, X):
|
||||
return f"<{o.val}>"
|
||||
|
||||
class MyDecoder(flask.json.JSONDecoder):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault("object_hook", self.object_hook)
|
||||
flask.json.JSONDecoder.__init__(self, *args, **kwargs)
|
||||
return DefaultJSONProvider.default(o)
|
||||
|
||||
class CustomProvider(DefaultJSONProvider):
|
||||
def object_hook(self, obj):
|
||||
if len(obj) == 1 and "_foo" in obj:
|
||||
return X(obj["_foo"])
|
||||
|
||||
return obj
|
||||
|
||||
app.json_encoder = MyEncoder
|
||||
app.json_decoder = MyDecoder
|
||||
def loads(self, s, **kwargs):
|
||||
kwargs.setdefault("object_hook", self.object_hook)
|
||||
return super().loads(s, **kwargs)
|
||||
|
||||
app.json = CustomProvider(app)
|
||||
app.json.default = default
|
||||
|
||||
@app.route("/", methods=["POST"])
|
||||
def index():
|
||||
|
|
@ -256,49 +257,6 @@ def test_json_customization(app, client):
|
|||
assert rv.data == b'"<42>"'
|
||||
|
||||
|
||||
def test_blueprint_json_customization(app, client):
|
||||
class X:
|
||||
__slots__ = ("val",)
|
||||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
class MyEncoder(flask.json.JSONEncoder):
|
||||
def default(self, o):
|
||||
if isinstance(o, X):
|
||||
return f"<{o.val}>"
|
||||
|
||||
return flask.json.JSONEncoder.default(self, o)
|
||||
|
||||
class MyDecoder(flask.json.JSONDecoder):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault("object_hook", self.object_hook)
|
||||
flask.json.JSONDecoder.__init__(self, *args, **kwargs)
|
||||
|
||||
def object_hook(self, obj):
|
||||
if len(obj) == 1 and "_foo" in obj:
|
||||
return X(obj["_foo"])
|
||||
|
||||
return obj
|
||||
|
||||
bp = flask.Blueprint("bp", __name__)
|
||||
bp.json_encoder = MyEncoder
|
||||
bp.json_decoder = MyDecoder
|
||||
|
||||
@bp.route("/bp", methods=["POST"])
|
||||
def index():
|
||||
return flask.json.dumps(flask.request.get_json()["x"])
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
rv = client.post(
|
||||
"/bp",
|
||||
data=flask.json.dumps({"x": {"_foo": 42}}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert rv.data == b'"<42>"'
|
||||
|
||||
|
||||
def _has_encoding(name):
|
||||
try:
|
||||
import codecs
|
||||
|
|
@ -330,8 +288,7 @@ def test_modified_url_encoding(app, client):
|
|||
|
||||
def test_json_key_sorting(app, client):
|
||||
app.debug = True
|
||||
|
||||
assert app.config["JSON_SORT_KEYS"]
|
||||
assert app.json.sort_keys
|
||||
d = dict.fromkeys(range(20), "foo")
|
||||
|
||||
@app.route("/")
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ def test_path_is_url(app):
|
|||
|
||||
def test_environbuilder_json_dumps(app):
|
||||
"""EnvironBuilder.json_dumps() takes settings from the app."""
|
||||
app.config["JSON_AS_ASCII"] = False
|
||||
app.json.ensure_ascii = False
|
||||
eb = EnvironBuilder(app, json="\u20ac")
|
||||
assert eb.input_stream.read().decode("utf8") == '"\u20ac"'
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue