Format: text/x-python
⬇ Download File (7.5 KB)
   1  from __future__ import annotations
   2  
   3  import dataclasses
   4  import decimal
   5  import json
   6  import typing as t
   7  import uuid
   8  import weakref
   9  from datetime import date
  10  
  11  from werkzeug.http import http_date
  12  
  13  if t.TYPE_CHECKING:  # pragma: no cover
  14      from werkzeug.sansio.response import Response
  15  
  16      from ..sansio.app import App
  17  
  18  
  19  class JSONProvider:
  20      """A standard set of JSON operations for an application. Subclasses
  21      of this can be used to customize JSON behavior or use different
  22      JSON libraries.
  23  
  24      To implement a provider for a specific library, subclass this base
  25      class and implement at least :meth:`dumps` and :meth:`loads`. All
  26      other methods have default implementations.
  27  
  28      To use a different provider, either subclass ``Flask`` and set
  29      :attr:`~flask.Flask.json_provider_class` to a provider class, or set
  30      :attr:`app.json <flask.Flask.json>` to an instance of the class.
  31  
  32      :param app: An application instance. This will be stored as a
  33          :class:`weakref.proxy` on the :attr:`_app` attribute.
  34  
  35      .. versionadded:: 2.2
  36      """
  37  
  38      def __init__(self, app: App) -> None:
  39          self._app: App = weakref.proxy(app)
  40  
  41      def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
  42          """Serialize data as JSON.
  43  
  44          :param obj: The data to serialize.
  45          :param kwargs: May be passed to the underlying JSON library.
  46          """
  47          raise NotImplementedError
  48  
  49      def dump(self, obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
  50          """Serialize data as JSON and write to a file.
  51  
  52          :param obj: The data to serialize.
  53          :param fp: A file opened for writing text. Should use the UTF-8
  54              encoding to be valid JSON.
  55          :param kwargs: May be passed to the underlying JSON library.
  56          """
  57          fp.write(self.dumps(obj, **kwargs))
  58  
  59      def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
  60          """Deserialize data as JSON.
  61  
  62          :param s: Text or UTF-8 bytes.
  63          :param kwargs: May be passed to the underlying JSON library.
  64          """
  65          raise NotImplementedError
  66  
  67      def load(self, fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any:
  68          """Deserialize data as JSON read from a file.
  69  
  70          :param fp: A file opened for reading text or UTF-8 bytes.
  71          :param kwargs: May be passed to the underlying JSON library.
  72          """
  73          return self.loads(fp.read(), **kwargs)
  74  
  75      def _prepare_response_obj(
  76          self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any]
  77      ) -> t.Any:
  78          if args and kwargs:
  79              raise TypeError("app.json.response() takes either args or kwargs, not both")
  80  
  81          if not args and not kwargs:
  82              return None
  83  
  84          if len(args) == 1:
  85              return args[0]
  86  
  87          return args or kwargs
  88  
  89      def response(self, *args: t.Any, **kwargs: t.Any) -> Response:
  90          """Serialize the given arguments as JSON, and return a
  91          :class:`~flask.Response` object with the ``application/json``
  92          mimetype.
  93  
  94          The :func:`~flask.json.jsonify` function calls this method for
  95          the current application.
  96  
  97          Either positional or keyword arguments can be given, not both.
  98          If no arguments are given, ``None`` is serialized.
  99  
 100          :param args: A single value to serialize, or multiple values to
 101              treat as a list to serialize.
 102          :param kwargs: Treat as a dict to serialize.
 103          """
 104          obj = self._prepare_response_obj(args, kwargs)
 105          return self._app.response_class(self.dumps(obj), mimetype="application/json")
 106  
 107  
 108  def _default(o: t.Any) -> t.Any:
 109      if isinstance(o, date):
 110          return http_date(o)
 111  
 112      if isinstance(o, (decimal.Decimal, uuid.UUID)):
 113          return str(o)
 114  
 115      if dataclasses and dataclasses.is_dataclass(o):
 116          return dataclasses.asdict(o)  # type: ignore[arg-type]
 117  
 118      if hasattr(o, "__html__"):
 119          return str(o.__html__())
 120  
 121      raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
 122  
 123  
 124  class DefaultJSONProvider(JSONProvider):
 125      """Provide JSON operations using Python's built-in :mod:`json`
 126      library. Serializes the following additional data types:
 127  
 128      -   :class:`datetime.datetime` and :class:`datetime.date` are
 129          serialized to :rfc:`822` strings. This is the same as the HTTP
 130          date format.
 131      -   :class:`uuid.UUID` is serialized to a string.
 132      -   :class:`dataclasses.dataclass` is passed to
 133          :func:`dataclasses.asdict`.
 134      -   :class:`~markupsafe.Markup` (or any object with a ``__html__``
 135          method) will call the ``__html__`` method to get a string.
 136      """
 137  
 138      default: t.Callable[[t.Any], t.Any] = staticmethod(_default)  # type: ignore[assignment]
 139      """Apply this function to any object that :meth:`json.dumps` does
 140      not know how to serialize. It should return a valid JSON type or
 141      raise a ``TypeError``.
 142      """
 143  
 144      ensure_ascii = True
 145      """Replace non-ASCII characters with escape sequences. This may be
 146      more compatible with some clients, but can be disabled for better
 147      performance and size.
 148      """
 149  
 150      sort_keys = True
 151      """Sort the keys in any serialized dicts. This may be useful for
 152      some caching situations, but can be disabled for better performance.
 153      When enabled, keys must all be strings, they are not converted
 154      before sorting.
 155      """
 156  
 157      compact: bool | None = None
 158      """If ``True``, or ``None`` out of debug mode, the :meth:`response`
 159      output will not add indentation, newlines, or spaces. If ``False``,
 160      or ``None`` in debug mode, it will use a non-compact representation.
 161      """
 162  
 163      mimetype = "application/json"
 164      """The mimetype set in :meth:`response`."""
 165  
 166      def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
 167          """Serialize data as JSON to a string.
 168  
 169          Keyword arguments are passed to :func:`json.dumps`. Sets some
 170          parameter defaults from the :attr:`default`,
 171          :attr:`ensure_ascii`, and :attr:`sort_keys` attributes.
 172  
 173          :param obj: The data to serialize.
 174          :param kwargs: Passed to :func:`json.dumps`.
 175          """
 176          kwargs.setdefault("default", self.default)
 177          kwargs.setdefault("ensure_ascii", self.ensure_ascii)
 178          kwargs.setdefault("sort_keys", self.sort_keys)
 179          return json.dumps(obj, **kwargs)
 180  
 181      def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
 182          """Deserialize data as JSON from a string or bytes.
 183  
 184          :param s: Text or UTF-8 bytes.
 185          :param kwargs: Passed to :func:`json.loads`.
 186          """
 187          return json.loads(s, **kwargs)
 188  
 189      def response(self, *args: t.Any, **kwargs: t.Any) -> Response:
 190          """Serialize the given arguments as JSON, and return a
 191          :class:`~flask.Response` object with it. The response mimetype
 192          will be "application/json" and can be changed with
 193          :attr:`mimetype`.
 194  
 195          If :attr:`compact` is ``False`` or debug mode is enabled, the
 196          output will be formatted to be easier to read.
 197  
 198          Either positional or keyword arguments can be given, not both.
 199          If no arguments are given, ``None`` is serialized.
 200  
 201          :param args: A single value to serialize, or multiple values to
 202              treat as a list to serialize.
 203          :param kwargs: Treat as a dict to serialize.
 204          """
 205          obj = self._prepare_response_obj(args, kwargs)
 206          dump_args: dict[str, t.Any] = {}
 207  
 208          if (self.compact is None and self._app.debug) or self.compact is False:
 209              dump_args.setdefault("indent", 2)
 210          else:
 211              dump_args.setdefault("separators", (",", ":"))
 212  
 213          return self._app.response_class(
 214              f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
 215          )
 216