__init__.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. import decimal
  2. import io
  3. import json as _json
  4. import typing as t
  5. import uuid
  6. import warnings
  7. from datetime import date
  8. from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps
  9. from werkzeug.http import http_date
  10. from ..globals import current_app
  11. from ..globals import request
  12. if t.TYPE_CHECKING:
  13. from ..app import Flask
  14. from ..wrappers import Response
  15. try:
  16. import dataclasses
  17. except ImportError:
  18. # Python < 3.7
  19. dataclasses = None # type: ignore
  20. class JSONEncoder(_json.JSONEncoder):
  21. """The default JSON encoder. Handles extra types compared to the
  22. built-in :class:`json.JSONEncoder`.
  23. - :class:`datetime.datetime` and :class:`datetime.date` are
  24. serialized to :rfc:`822` strings. This is the same as the HTTP
  25. date format.
  26. - :class:`uuid.UUID` is serialized to a string.
  27. - :class:`dataclasses.dataclass` is passed to
  28. :func:`dataclasses.asdict`.
  29. - :class:`~markupsafe.Markup` (or any object with a ``__html__``
  30. method) will call the ``__html__`` method to get a string.
  31. Assign a subclass of this to :attr:`flask.Flask.json_encoder` or
  32. :attr:`flask.Blueprint.json_encoder` to override the default.
  33. """
  34. def default(self, o: t.Any) -> t.Any:
  35. """Convert ``o`` to a JSON serializable type. See
  36. :meth:`json.JSONEncoder.default`. Python does not support
  37. overriding how basic types like ``str`` or ``list`` are
  38. serialized, they are handled before this method.
  39. """
  40. if isinstance(o, date):
  41. return http_date(o)
  42. if isinstance(o, (decimal.Decimal, uuid.UUID)):
  43. return str(o)
  44. if dataclasses and dataclasses.is_dataclass(o):
  45. return dataclasses.asdict(o)
  46. if hasattr(o, "__html__"):
  47. return str(o.__html__())
  48. return super().default(o)
  49. class JSONDecoder(_json.JSONDecoder):
  50. """The default JSON decoder.
  51. This does not change any behavior from the built-in
  52. :class:`json.JSONDecoder`.
  53. Assign a subclass of this to :attr:`flask.Flask.json_decoder` or
  54. :attr:`flask.Blueprint.json_decoder` to override the default.
  55. """
  56. def _dump_arg_defaults(
  57. kwargs: t.Dict[str, t.Any], app: t.Optional["Flask"] = None
  58. ) -> None:
  59. """Inject default arguments for dump functions."""
  60. if app is None:
  61. app = current_app
  62. if app:
  63. cls = app.json_encoder
  64. bp = app.blueprints.get(request.blueprint) if request else None # type: ignore
  65. if bp is not None and bp.json_encoder is not None:
  66. cls = bp.json_encoder
  67. kwargs.setdefault("cls", cls)
  68. kwargs.setdefault("ensure_ascii", app.config["JSON_AS_ASCII"])
  69. kwargs.setdefault("sort_keys", app.config["JSON_SORT_KEYS"])
  70. else:
  71. kwargs.setdefault("sort_keys", True)
  72. kwargs.setdefault("cls", JSONEncoder)
  73. def _load_arg_defaults(
  74. kwargs: t.Dict[str, t.Any], app: t.Optional["Flask"] = None
  75. ) -> None:
  76. """Inject default arguments for load functions."""
  77. if app is None:
  78. app = current_app
  79. if app:
  80. cls = app.json_decoder
  81. bp = app.blueprints.get(request.blueprint) if request else None # type: ignore
  82. if bp is not None and bp.json_decoder is not None:
  83. cls = bp.json_decoder
  84. kwargs.setdefault("cls", cls)
  85. else:
  86. kwargs.setdefault("cls", JSONDecoder)
  87. def dumps(obj: t.Any, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> str:
  88. """Serialize an object to a string of JSON.
  89. Takes the same arguments as the built-in :func:`json.dumps`, with
  90. some defaults from application configuration.
  91. :param obj: Object to serialize to JSON.
  92. :param app: Use this app's config instead of the active app context
  93. or defaults.
  94. :param kwargs: Extra arguments passed to :func:`json.dumps`.
  95. .. versionchanged:: 2.0.2
  96. :class:`decimal.Decimal` is supported by converting to a string.
  97. .. versionchanged:: 2.0
  98. ``encoding`` is deprecated and will be removed in Flask 2.1.
  99. .. versionchanged:: 1.0.3
  100. ``app`` can be passed directly, rather than requiring an app
  101. context for configuration.
  102. """
  103. _dump_arg_defaults(kwargs, app=app)
  104. encoding = kwargs.pop("encoding", None)
  105. rv = _json.dumps(obj, **kwargs)
  106. if encoding is not None:
  107. warnings.warn(
  108. "'encoding' is deprecated and will be removed in Flask 2.1.",
  109. DeprecationWarning,
  110. stacklevel=2,
  111. )
  112. if isinstance(rv, str):
  113. return rv.encode(encoding) # type: ignore
  114. return rv
  115. def dump(
  116. obj: t.Any, fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any
  117. ) -> None:
  118. """Serialize an object to JSON written to a file object.
  119. Takes the same arguments as the built-in :func:`json.dump`, with
  120. some defaults from application configuration.
  121. :param obj: Object to serialize to JSON.
  122. :param fp: File object to write JSON to.
  123. :param app: Use this app's config instead of the active app context
  124. or defaults.
  125. :param kwargs: Extra arguments passed to :func:`json.dump`.
  126. .. versionchanged:: 2.0
  127. Writing to a binary file, and the ``encoding`` argument, is
  128. deprecated and will be removed in Flask 2.1.
  129. """
  130. _dump_arg_defaults(kwargs, app=app)
  131. encoding = kwargs.pop("encoding", None)
  132. show_warning = encoding is not None
  133. try:
  134. fp.write("")
  135. except TypeError:
  136. show_warning = True
  137. fp = io.TextIOWrapper(fp, encoding or "utf-8") # type: ignore
  138. if show_warning:
  139. warnings.warn(
  140. "Writing to a binary file, and the 'encoding' argument, is"
  141. " deprecated and will be removed in Flask 2.1.",
  142. DeprecationWarning,
  143. stacklevel=2,
  144. )
  145. _json.dump(obj, fp, **kwargs)
  146. def loads(s: str, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any:
  147. """Deserialize an object from a string of JSON.
  148. Takes the same arguments as the built-in :func:`json.loads`, with
  149. some defaults from application configuration.
  150. :param s: JSON string to deserialize.
  151. :param app: Use this app's config instead of the active app context
  152. or defaults.
  153. :param kwargs: Extra arguments passed to :func:`json.loads`.
  154. .. versionchanged:: 2.0
  155. ``encoding`` is deprecated and will be removed in Flask 2.1. The
  156. data must be a string or UTF-8 bytes.
  157. .. versionchanged:: 1.0.3
  158. ``app`` can be passed directly, rather than requiring an app
  159. context for configuration.
  160. """
  161. _load_arg_defaults(kwargs, app=app)
  162. encoding = kwargs.pop("encoding", None)
  163. if encoding is not None:
  164. warnings.warn(
  165. "'encoding' is deprecated and will be removed in Flask 2.1."
  166. " The data must be a string or UTF-8 bytes.",
  167. DeprecationWarning,
  168. stacklevel=2,
  169. )
  170. if isinstance(s, bytes):
  171. s = s.decode(encoding)
  172. return _json.loads(s, **kwargs)
  173. def load(fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any:
  174. """Deserialize an object from JSON read from a file object.
  175. Takes the same arguments as the built-in :func:`json.load`, with
  176. some defaults from application configuration.
  177. :param fp: File object to read JSON from.
  178. :param app: Use this app's config instead of the active app context
  179. or defaults.
  180. :param kwargs: Extra arguments passed to :func:`json.load`.
  181. .. versionchanged:: 2.0
  182. ``encoding`` is deprecated and will be removed in Flask 2.1. The
  183. file must be text mode, or binary mode with UTF-8 bytes.
  184. """
  185. _load_arg_defaults(kwargs, app=app)
  186. encoding = kwargs.pop("encoding", None)
  187. if encoding is not None:
  188. warnings.warn(
  189. "'encoding' is deprecated and will be removed in Flask 2.1."
  190. " The file must be text mode, or binary mode with UTF-8"
  191. " bytes.",
  192. DeprecationWarning,
  193. stacklevel=2,
  194. )
  195. if isinstance(fp.read(0), bytes):
  196. fp = io.TextIOWrapper(fp, encoding) # type: ignore
  197. return _json.load(fp, **kwargs)
  198. def htmlsafe_dumps(obj: t.Any, **kwargs: t.Any) -> str:
  199. """Serialize an object to a string of JSON with :func:`dumps`, then
  200. replace HTML-unsafe characters with Unicode escapes and mark the
  201. result safe with :class:`~markupsafe.Markup`.
  202. This is available in templates as the ``|tojson`` filter.
  203. The returned string is safe to render in HTML documents and
  204. ``<script>`` tags. The exception is in HTML attributes that are
  205. double quoted; either use single quotes or the ``|forceescape``
  206. filter.
  207. .. versionchanged:: 2.0
  208. Uses :func:`jinja2.utils.htmlsafe_json_dumps`. The returned
  209. value is marked safe by wrapping in :class:`~markupsafe.Markup`.
  210. .. versionchanged:: 0.10
  211. Single quotes are escaped, making this safe to use in HTML,
  212. ``<script>`` tags, and single-quoted attributes without further
  213. escaping.
  214. """
  215. return _jinja_htmlsafe_dumps(obj, dumps=dumps, **kwargs)
  216. def htmlsafe_dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
  217. """Serialize an object to JSON written to a file object, replacing
  218. HTML-unsafe characters with Unicode escapes. See
  219. :func:`htmlsafe_dumps` and :func:`dumps`.
  220. """
  221. fp.write(htmlsafe_dumps(obj, **kwargs))
  222. def jsonify(*args: t.Any, **kwargs: t.Any) -> "Response":
  223. """Serialize data to JSON and wrap it in a :class:`~flask.Response`
  224. with the :mimetype:`application/json` mimetype.
  225. Uses :func:`dumps` to serialize the data, but ``args`` and
  226. ``kwargs`` are treated as data rather than arguments to
  227. :func:`json.dumps`.
  228. 1. Single argument: Treated as a single value.
  229. 2. Multiple arguments: Treated as a list of values.
  230. ``jsonify(1, 2, 3)`` is the same as ``jsonify([1, 2, 3])``.
  231. 3. Keyword arguments: Treated as a dict of values.
  232. ``jsonify(data=data, errors=errors)`` is the same as
  233. ``jsonify({"data": data, "errors": errors})``.
  234. 4. Passing both arguments and keyword arguments is not allowed as
  235. it's not clear what should happen.
  236. .. code-block:: python
  237. from flask import jsonify
  238. @app.route("/users/me")
  239. def get_current_user():
  240. return jsonify(
  241. username=g.user.username,
  242. email=g.user.email,
  243. id=g.user.id,
  244. )
  245. Will return a JSON response like this:
  246. .. code-block:: javascript
  247. {
  248. "username": "admin",
  249. "email": "admin@localhost",
  250. "id": 42
  251. }
  252. The default output omits indents and spaces after separators. In
  253. debug mode or if :data:`JSONIFY_PRETTYPRINT_REGULAR` is ``True``,
  254. the output will be formatted to be easier to read.
  255. .. versionchanged:: 2.0.2
  256. :class:`decimal.Decimal` is supported by converting to a string.
  257. .. versionchanged:: 0.11
  258. Added support for serializing top-level arrays. This introduces
  259. a security risk in ancient browsers. See :ref:`security-json`.
  260. .. versionadded:: 0.2
  261. """
  262. indent = None
  263. separators = (",", ":")
  264. if current_app.config["JSONIFY_PRETTYPRINT_REGULAR"] or current_app.debug:
  265. indent = 2
  266. separators = (", ", ": ")
  267. if args and kwargs:
  268. raise TypeError("jsonify() behavior undefined when passed both args and kwargs")
  269. elif len(args) == 1: # single args are passed directly to dumps()
  270. data = args[0]
  271. else:
  272. data = args or kwargs
  273. return current_app.response_class(
  274. f"{dumps(data, indent=indent, separators=separators)}\n",
  275. mimetype=current_app.config["JSONIFY_MIMETYPE"],
  276. )