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
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue