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

101 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +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 

13from ..globals import request 

14 

15if t.TYPE_CHECKING: # pragma: no cover 

16 from ..app import Flask 

17 from ..wrappers import Response 

18 

19 

20class JSONProvider: 

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

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

23 JSON libraries. 

24 

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

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

27 other methods have default implementations. 

28 

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

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

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

32 

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

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

35 

36 .. versionadded:: 2.2 

37 """ 

38 

39 def __init__(self, app: Flask) -> None: 

40 self._app = weakref.proxy(app) 

41 

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

43 """Serialize data as JSON. 

44 

45 :param obj: The data to serialize. 

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

47 """ 

48 raise NotImplementedError 

49 

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

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

52 

53 :param obj: The data to serialize. 

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

55 encoding to be valid JSON. 

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

57 """ 

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

59 

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

61 """Deserialize data as JSON. 

62 

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

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

65 """ 

66 raise NotImplementedError 

67 

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

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

70 

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

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

73 """ 

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

75 

76 def _prepare_response_obj( 

77 self, args: t.Tuple[t.Any, ...], kwargs: t.Dict[str, t.Any] 

78 ) -> t.Any: 

79 if args and kwargs: 

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

81 

82 if not args and not kwargs: 

83 return None 

84 

85 if len(args) == 1: 

86 return args[0] 

87 

88 return args or kwargs 

89 

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

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

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

93 mimetype. 

94 

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

96 the current application. 

97 

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

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

100 

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

102 treat as a list to serialize. 

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

104 """ 

105 obj = self._prepare_response_obj(args, kwargs) 

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

107 

108 

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

110 if isinstance(o, date): 

111 return http_date(o) 

112 

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

114 return str(o) 

115 

116 if dataclasses and dataclasses.is_dataclass(o): 

117 return dataclasses.asdict(o) 

118 

119 if hasattr(o, "__html__"): 

120 return str(o.__html__()) 

121 

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

123 

124 

125class DefaultJSONProvider(JSONProvider): 

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

127 library. Serializes the following additional data types: 

128 

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

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

131 date format. 

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

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

134 :func:`dataclasses.asdict`. 

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

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

137 """ 

138 

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

140 _default 

141 ) # type: ignore[assignment] 

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

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

144 raise a ``TypeError``. 

145 """ 

146 

147 ensure_ascii = True 

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

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

150 performance and size. 

151 """ 

152 

153 sort_keys = True 

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

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

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

157 before sorting. 

158 """ 

159 

160 compact: bool | None = None 

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

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

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

164 """ 

165 

166 mimetype = "application/json" 

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

168 

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

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

171 

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

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

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

175 

176 :param obj: The data to serialize. 

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

178 """ 

179 cls = self._app._json_encoder 

180 bp = self._app.blueprints.get(request.blueprint) if request else None 

181 

182 if bp is not None and bp._json_encoder is not None: 

183 cls = bp._json_encoder 

184 

185 if cls is not None: 

186 import warnings 

187 

188 warnings.warn( 

189 "Setting 'json_encoder' on the app or a blueprint is" 

190 " deprecated and will be removed in Flask 2.3." 

191 " Customize 'app.json' instead.", 

192 DeprecationWarning, 

193 ) 

194 kwargs.setdefault("cls", cls) 

195 

196 if "default" not in cls.__dict__: 

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

198 else: 

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

200 

201 ensure_ascii = self._app.config["JSON_AS_ASCII"] 

202 sort_keys = self._app.config["JSON_SORT_KEYS"] 

203 

204 if ensure_ascii is not None: 

205 import warnings 

206 

207 warnings.warn( 

208 "The 'JSON_AS_ASCII' config key is deprecated and will" 

209 " be removed in Flask 2.3. Set 'app.json.ensure_ascii'" 

210 " instead.", 

211 DeprecationWarning, 

212 ) 

213 else: 

214 ensure_ascii = self.ensure_ascii 

215 

216 if sort_keys is not None: 

217 import warnings 

218 

219 warnings.warn( 

220 "The 'JSON_SORT_KEYS' config key is deprecated and will" 

221 " be removed in Flask 2.3. Set 'app.json.sort_keys'" 

222 " instead.", 

223 DeprecationWarning, 

224 ) 

225 else: 

226 sort_keys = self.sort_keys 

227 

228 kwargs.setdefault("ensure_ascii", ensure_ascii) 

229 kwargs.setdefault("sort_keys", sort_keys) 

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

231 

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

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

234 

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

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

237 """ 

238 cls = self._app._json_decoder 

239 bp = self._app.blueprints.get(request.blueprint) if request else None 

240 

241 if bp is not None and bp._json_decoder is not None: 

242 cls = bp._json_decoder 

243 

244 if cls is not None: 

245 import warnings 

246 

247 warnings.warn( 

248 "Setting 'json_decoder' on the app or a blueprint is" 

249 " deprecated and will be removed in Flask 2.3." 

250 " Customize 'app.json' instead.", 

251 DeprecationWarning, 

252 ) 

253 kwargs.setdefault("cls", cls) 

254 

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

256 

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

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

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

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

261 :attr:`mimetype`. 

262 

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

264 output will be formatted to be easier to read. 

265 

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

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

268 

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

270 treat as a list to serialize. 

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

272 """ 

273 obj = self._prepare_response_obj(args, kwargs) 

274 dump_args: t.Dict[str, t.Any] = {} 

275 pretty = self._app.config["JSONIFY_PRETTYPRINT_REGULAR"] 

276 mimetype = self._app.config["JSONIFY_MIMETYPE"] 

277 

278 if pretty is not None: 

279 import warnings 

280 

281 warnings.warn( 

282 "The 'JSONIFY_PRETTYPRINT_REGULAR' config key is" 

283 " deprecated and will be removed in Flask 2.3. Set" 

284 " 'app.json.compact' instead.", 

285 DeprecationWarning, 

286 ) 

287 compact: bool | None = not pretty 

288 else: 

289 compact = self.compact 

290 

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

292 dump_args.setdefault("indent", 2) 

293 else: 

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

295 

296 if mimetype is not None: 

297 import warnings 

298 

299 warnings.warn( 

300 "The 'JSONIFY_MIMETYPE' config key is deprecated and" 

301 " will be removed in Flask 2.3. Set 'app.json.mimetype'" 

302 " instead.", 

303 DeprecationWarning, 

304 ) 

305 else: 

306 mimetype = self.mimetype 

307 

308 return self._app.response_class( 

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

310 )