Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/flask/json/provider.py: 44%
61 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-22 06:29 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-22 06:29 +0000
1from __future__ import annotations
3import dataclasses
4import decimal
5import json
6import typing as t
7import uuid
8import weakref
9from datetime import date
11from werkzeug.http import http_date
13if t.TYPE_CHECKING: # pragma: no cover
14 from werkzeug.sansio.response import Response
16 from ..sansio.app import App
19class 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.
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.
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.
32 :param app: An application instance. This will be stored as a
33 :class:`weakref.proxy` on the :attr:`_app` attribute.
35 .. versionadded:: 2.2
36 """
38 def __init__(self, app: App) -> None:
39 self._app: App = weakref.proxy(app)
41 def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
42 """Serialize data as JSON.
44 :param obj: The data to serialize.
45 :param kwargs: May be passed to the underlying JSON library.
46 """
47 raise NotImplementedError
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.
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))
59 def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
60 """Deserialize data as JSON.
62 :param s: Text or UTF-8 bytes.
63 :param kwargs: May be passed to the underlying JSON library.
64 """
65 raise NotImplementedError
67 def load(self, fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any:
68 """Deserialize data as JSON read from a file.
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)
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")
81 if not args and not kwargs:
82 return None
84 if len(args) == 1:
85 return args[0]
87 return args or kwargs
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.
94 The :func:`~flask.json.jsonify` function calls this method for
95 the current application.
97 Either positional or keyword arguments can be given, not both.
98 If no arguments are given, ``None`` is serialized.
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")
108def _default(o: t.Any) -> t.Any:
109 if isinstance(o, date):
110 return http_date(o)
112 if isinstance(o, (decimal.Decimal, uuid.UUID)):
113 return str(o)
115 if dataclasses and dataclasses.is_dataclass(o):
116 return dataclasses.asdict(o)
118 if hasattr(o, "__html__"):
119 return str(o.__html__())
121 raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
124class DefaultJSONProvider(JSONProvider):
125 """Provide JSON operations using Python's built-in :mod:`json`
126 library. Serializes the following additional data types:
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 """
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 """
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 """
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 """
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 """
163 mimetype = "application/json"
164 """The mimetype set in :meth:`response`."""
166 def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
167 """Serialize data as JSON to a string.
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.
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)
181 def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
182 """Deserialize data as JSON from a string or bytes.
184 :param s: Text or UTF-8 bytes.
185 :param kwargs: Passed to :func:`json.loads`.
186 """
187 return json.loads(s, **kwargs)
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`.
195 If :attr:`compact` is ``False`` or debug mode is enabled, the
196 output will be formatted to be easier to read.
198 Either positional or keyword arguments can be given, not both.
199 If no arguments are given, ``None`` is serialized.
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] = {}
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", (",", ":"))
213 return self._app.response_class(
214 f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
215 )