Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/flask/json/provider.py: 69%

61 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-09 07:17 +0000

1from __future__ import annotations 

2 

3import dataclasses 

4import decimal 

5import json 

6import typing as t 

7import uuid 

8import weakref 

9from datetime import date 

10 

11from werkzeug.http import http_date 

12 

13if t.TYPE_CHECKING: # pragma: no cover 

14 from ..sansio.app import App 

15 from ..wrappers import Response 

16 

17 

18class JSONProvider: 

19 """A standard set of JSON operations for an application. Subclasses 

20 of this can be used to customize JSON behavior or use different 

21 JSON libraries. 

22 

23 To implement a provider for a specific library, subclass this base 

24 class and implement at least :meth:`dumps` and :meth:`loads`. All 

25 other methods have default implementations. 

26 

27 To use a different provider, either subclass ``Flask`` and set 

28 :attr:`~flask.Flask.json_provider_class` to a provider class, or set 

29 :attr:`app.json <flask.Flask.json>` to an instance of the class. 

30 

31 :param app: An application instance. This will be stored as a 

32 :class:`weakref.proxy` on the :attr:`_app` attribute. 

33 

34 .. versionadded:: 2.2 

35 """ 

36 

37 def __init__(self, app: App) -> None: 

38 self._app = weakref.proxy(app) 

39 

40 def dumps(self, obj: t.Any, **kwargs: t.Any) -> str: 

41 """Serialize data as JSON. 

42 

43 :param obj: The data to serialize. 

44 :param kwargs: May be passed to the underlying JSON library. 

45 """ 

46 raise NotImplementedError 

47 

48 def dump(self, obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None: 

49 """Serialize data as JSON and write to a file. 

50 

51 :param obj: The data to serialize. 

52 :param fp: A file opened for writing text. Should use the UTF-8 

53 encoding to be valid JSON. 

54 :param kwargs: May be passed to the underlying JSON library. 

55 """ 

56 fp.write(self.dumps(obj, **kwargs)) 

57 

58 def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: 

59 """Deserialize data as JSON. 

60 

61 :param s: Text or UTF-8 bytes. 

62 :param kwargs: May be passed to the underlying JSON library. 

63 """ 

64 raise NotImplementedError 

65 

66 def load(self, fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any: 

67 """Deserialize data as JSON read from a file. 

68 

69 :param fp: A file opened for reading text or UTF-8 bytes. 

70 :param kwargs: May be passed to the underlying JSON library. 

71 """ 

72 return self.loads(fp.read(), **kwargs) 

73 

74 def _prepare_response_obj( 

75 self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any] 

76 ) -> t.Any: 

77 if args and kwargs: 

78 raise TypeError("app.json.response() takes either args or kwargs, not both") 

79 

80 if not args and not kwargs: 

81 return None 

82 

83 if len(args) == 1: 

84 return args[0] 

85 

86 return args or kwargs 

87 

88 def response(self, *args: t.Any, **kwargs: t.Any) -> Response: 

89 """Serialize the given arguments as JSON, and return a 

90 :class:`~flask.Response` object with the ``application/json`` 

91 mimetype. 

92 

93 The :func:`~flask.json.jsonify` function calls this method for 

94 the current application. 

95 

96 Either positional or keyword arguments can be given, not both. 

97 If no arguments are given, ``None`` is serialized. 

98 

99 :param args: A single value to serialize, or multiple values to 

100 treat as a list to serialize. 

101 :param kwargs: Treat as a dict to serialize. 

102 """ 

103 obj = self._prepare_response_obj(args, kwargs) 

104 return self._app.response_class(self.dumps(obj), mimetype="application/json") 

105 

106 

107def _default(o: t.Any) -> t.Any: 

108 if isinstance(o, date): 

109 return http_date(o) 

110 

111 if isinstance(o, (decimal.Decimal, uuid.UUID)): 

112 return str(o) 

113 

114 if dataclasses and dataclasses.is_dataclass(o): 

115 return dataclasses.asdict(o) 

116 

117 if hasattr(o, "__html__"): 

118 return str(o.__html__()) 

119 

120 raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable") 

121 

122 

123class DefaultJSONProvider(JSONProvider): 

124 """Provide JSON operations using Python's built-in :mod:`json` 

125 library. Serializes the following additional data types: 

126 

127 - :class:`datetime.datetime` and :class:`datetime.date` are 

128 serialized to :rfc:`822` strings. This is the same as the HTTP 

129 date format. 

130 - :class:`uuid.UUID` is serialized to a string. 

131 - :class:`dataclasses.dataclass` is passed to 

132 :func:`dataclasses.asdict`. 

133 - :class:`~markupsafe.Markup` (or any object with a ``__html__`` 

134 method) will call the ``__html__`` method to get a string. 

135 """ 

136 

137 default: t.Callable[[t.Any], t.Any] = staticmethod( 

138 _default 

139 ) # type: ignore[assignment] 

140 """Apply this function to any object that :meth:`json.dumps` does 

141 not know how to serialize. It should return a valid JSON type or 

142 raise a ``TypeError``. 

143 """ 

144 

145 ensure_ascii = True 

146 """Replace non-ASCII characters with escape sequences. This may be 

147 more compatible with some clients, but can be disabled for better 

148 performance and size. 

149 """ 

150 

151 sort_keys = True 

152 """Sort the keys in any serialized dicts. This may be useful for 

153 some caching situations, but can be disabled for better performance. 

154 When enabled, keys must all be strings, they are not converted 

155 before sorting. 

156 """ 

157 

158 compact: bool | None = None 

159 """If ``True``, or ``None`` out of debug mode, the :meth:`response` 

160 output will not add indentation, newlines, or spaces. If ``False``, 

161 or ``None`` in debug mode, it will use a non-compact representation. 

162 """ 

163 

164 mimetype = "application/json" 

165 """The mimetype set in :meth:`response`.""" 

166 

167 def dumps(self, obj: t.Any, **kwargs: t.Any) -> str: 

168 """Serialize data as JSON to a string. 

169 

170 Keyword arguments are passed to :func:`json.dumps`. Sets some 

171 parameter defaults from the :attr:`default`, 

172 :attr:`ensure_ascii`, and :attr:`sort_keys` attributes. 

173 

174 :param obj: The data to serialize. 

175 :param kwargs: Passed to :func:`json.dumps`. 

176 """ 

177 kwargs.setdefault("default", self.default) 

178 kwargs.setdefault("ensure_ascii", self.ensure_ascii) 

179 kwargs.setdefault("sort_keys", self.sort_keys) 

180 return json.dumps(obj, **kwargs) 

181 

182 def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: 

183 """Deserialize data as JSON from a string or bytes. 

184 

185 :param s: Text or UTF-8 bytes. 

186 :param kwargs: Passed to :func:`json.loads`. 

187 """ 

188 return json.loads(s, **kwargs) 

189 

190 def response(self, *args: t.Any, **kwargs: t.Any) -> Response: 

191 """Serialize the given arguments as JSON, and return a 

192 :class:`~flask.Response` object with it. The response mimetype 

193 will be "application/json" and can be changed with 

194 :attr:`mimetype`. 

195 

196 If :attr:`compact` is ``False`` or debug mode is enabled, the 

197 output will be formatted to be easier to read. 

198 

199 Either positional or keyword arguments can be given, not both. 

200 If no arguments are given, ``None`` is serialized. 

201 

202 :param args: A single value to serialize, or multiple values to 

203 treat as a list to serialize. 

204 :param kwargs: Treat as a dict to serialize. 

205 """ 

206 obj = self._prepare_response_obj(args, kwargs) 

207 dump_args: dict[str, t.Any] = {} 

208 

209 if (self.compact is None and self._app.debug) or self.compact is False: 

210 dump_args.setdefault("indent", 2) 

211 else: 

212 dump_args.setdefault("separators", (",", ":")) 

213 

214 return self._app.response_class( 

215 f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype 

216 )