Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jsonpickle/backend.py: 58%

109 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:20 +0000

1from __future__ import absolute_import, division, unicode_literals 

2 

3from .compat import string_types 

4 

5 

6class JSONBackend(object): 

7 """Manages encoding and decoding using various backends. 

8 

9 It tries these modules in this order: 

10 simplejson, json, ujson 

11 

12 simplejson is a fast and popular backend and is tried first. 

13 json comes with Python and is tried second. 

14 

15 """ 

16 

17 def _verify(self): 

18 """Ensures that we've loaded at least one JSON backend.""" 

19 if self._verified: 

20 return 

21 raise AssertionError( 

22 'jsonpickle requires at least one of the ' 

23 'following:\n' 

24 ' python2.6, simplejson' 

25 ) 

26 

27 def encode(self, obj, indent=None, separators=None): 

28 """ 

29 Attempt to encode an object into JSON. 

30 

31 This tries the loaded backends in order and passes along the last 

32 exception if no backend is able to encode the object. 

33 

34 """ 

35 self._verify() 

36 

37 if not self._fallthrough: 

38 name = self._backend_names[0] 

39 return self.backend_encode(name, obj, indent=indent, separators=separators) 

40 

41 for idx, name in enumerate(self._backend_names): 

42 try: 

43 return self.backend_encode( 

44 name, obj, indent=indent, separators=separators 

45 ) 

46 except Exception as e: 

47 if idx == len(self._backend_names) - 1: 

48 raise e 

49 

50 # def dumps 

51 dumps = encode 

52 

53 def decode(self, string): 

54 """ 

55 Attempt to decode an object from a JSON string. 

56 

57 This tries the loaded backends in order and passes along the last 

58 exception if no backends are able to decode the string. 

59 

60 """ 

61 self._verify() 

62 

63 if not self._fallthrough: 

64 name = self._backend_names[0] 

65 return self.backend_decode(name, string) 

66 

67 for idx, name in enumerate(self._backend_names): 

68 try: 

69 return self.backend_decode(name, string) 

70 except self._decoder_exceptions[name] as e: 

71 if idx == len(self._backend_names) - 1: 

72 raise e 

73 else: 

74 pass # and try a more forgiving encoder 

75 

76 # def loads 

77 loads = decode 

78 

79 def __init__(self, fallthrough=True): 

80 # Whether we should fallthrough to the next backend 

81 self._fallthrough = fallthrough 

82 # The names of backends that have been successfully imported 

83 self._backend_names = [] 

84 

85 # A dictionary mapping backend names to encode/decode functions 

86 self._encoders = {} 

87 self._decoders = {} 

88 

89 # Options to pass to specific encoders 

90 self._encoder_options = {} 

91 

92 # Options to pass to specific decoders 

93 self._decoder_options = {} 

94 

95 # The exception class that is thrown when a decoding error occurs 

96 self._decoder_exceptions = {} 

97 

98 # Whether we've loaded any backends successfully 

99 self._verified = False 

100 

101 self.load_backend('simplejson') 

102 self.load_backend('json') 

103 self.load_backend('ujson') 

104 

105 # Defaults for various encoders 

106 json_opts = ((), {'sort_keys': False}) 

107 self._encoder_options = { 

108 'ujson': ((), {'sort_keys': False, 'escape_forward_slashes': False}), 

109 'json': json_opts, 

110 'simplejson': json_opts, 

111 'django.util.simplejson': json_opts, 

112 } 

113 

114 def enable_fallthrough(self, enable): 

115 """ 

116 Disable jsonpickle's fallthrough-on-error behavior 

117 

118 By default, jsonpickle tries the next backend when decoding or 

119 encoding using a backend fails. 

120 

121 This can make it difficult to force jsonpickle to use a specific 

122 backend, and catch errors, because the error will be suppressed and 

123 may not be raised by the subsequent backend. 

124 

125 Calling `enable_backend(False)` will make jsonpickle immediately 

126 re-raise any exceptions raised by the backends. 

127 

128 """ 

129 self._fallthrough = enable 

130 

131 def _store(self, dct, backend, obj, name): 

132 try: 

133 dct[backend] = getattr(obj, name) 

134 except AttributeError: 

135 self.remove_backend(backend) 

136 return False 

137 return True 

138 

139 def load_backend(self, name, dumps='dumps', loads='loads', loads_exc=ValueError): 

140 """Load a JSON backend by name. 

141 

142 This method loads a backend and sets up references to that 

143 backend's loads/dumps functions and exception classes. 

144 

145 :param dumps: is the name of the backend's encode method. 

146 The method should take an object and return a string. 

147 Defaults to 'dumps'. 

148 :param loads: names the backend's method for the reverse 

149 operation -- returning a Python object from a string. 

150 :param loads_exc: can be either the name of the exception class 

151 used to denote decoding errors, or it can be a direct reference 

152 to the appropriate exception class itself. If it is a name, 

153 then the assumption is that an exception class of that name 

154 can be found in the backend module's namespace. 

155 :param load: names the backend's 'load' method. 

156 :param dump: names the backend's 'dump' method. 

157 :rtype bool: True on success, False if the backend could not be loaded. 

158 

159 """ 

160 try: 

161 # Load the JSON backend 

162 mod = __import__(name) 

163 except ImportError: 

164 return False 

165 

166 # Handle submodules, e.g. django.utils.simplejson 

167 try: 

168 for attr in name.split('.')[1:]: 

169 mod = getattr(mod, attr) 

170 except AttributeError: 

171 return False 

172 

173 if not self._store(self._encoders, name, mod, dumps) or not self._store( 

174 self._decoders, name, mod, loads 

175 ): 

176 return False 

177 

178 if isinstance(loads_exc, string_types): 

179 # This backend's decoder exception is part of the backend 

180 if not self._store(self._decoder_exceptions, name, mod, loads_exc): 

181 return False 

182 else: 

183 # simplejson uses ValueError 

184 self._decoder_exceptions[name] = loads_exc 

185 

186 # Setup the default args and kwargs for this encoder/decoder 

187 self._encoder_options.setdefault(name, ([], {})) 

188 self._decoder_options.setdefault(name, ([], {})) 

189 

190 # Add this backend to the list of candidate backends 

191 self._backend_names.append(name) 

192 

193 # Indicate that we successfully loaded a JSON backend 

194 self._verified = True 

195 return True 

196 

197 def remove_backend(self, name): 

198 """Remove all entries for a particular backend.""" 

199 self._encoders.pop(name, None) 

200 self._decoders.pop(name, None) 

201 self._decoder_exceptions.pop(name, None) 

202 self._decoder_options.pop(name, None) 

203 self._encoder_options.pop(name, None) 

204 if name in self._backend_names: 

205 self._backend_names.remove(name) 

206 self._verified = bool(self._backend_names) 

207 

208 def backend_encode(self, name, obj, indent=None, separators=None): 

209 optargs, optkwargs = self._encoder_options.get(name, ([], {})) 

210 encoder_kwargs = optkwargs.copy() 

211 if indent is not None: 

212 encoder_kwargs['indent'] = indent 

213 if separators is not None: 

214 encoder_kwargs['separators'] = separators 

215 encoder_args = (obj,) + tuple(optargs) 

216 return self._encoders[name](*encoder_args, **encoder_kwargs) 

217 

218 def backend_decode(self, name, string): 

219 optargs, optkwargs = self._decoder_options.get(name, ((), {})) 

220 decoder_kwargs = optkwargs.copy() 

221 return self._decoders[name](string, *optargs, **decoder_kwargs) 

222 

223 def set_preferred_backend(self, name): 

224 """ 

225 Set the preferred json backend. 

226 

227 If a preferred backend is set then jsonpickle tries to use it 

228 before any other backend. 

229 

230 For example:: 

231 

232 set_preferred_backend('simplejson') 

233 

234 If the backend is not one of the built-in jsonpickle backends 

235 (json/simplejson) then you must load the backend 

236 prior to calling set_preferred_backend. 

237 

238 AssertionError is raised if the backend has not been loaded. 

239 

240 """ 

241 if name in self._backend_names: 

242 self._backend_names.remove(name) 

243 self._backend_names.insert(0, name) 

244 else: 

245 errmsg = 'The "%s" backend has not been loaded.' % name 

246 raise AssertionError(errmsg) 

247 

248 def set_encoder_options(self, name, *args, **kwargs): 

249 """ 

250 Associate encoder-specific options with an encoder. 

251 

252 After calling set_encoder_options, any calls to jsonpickle's 

253 encode method will pass the supplied args and kwargs along to 

254 the appropriate backend's encode method. 

255 

256 For example:: 

257 

258 set_encoder_options('simplejson', sort_keys=True, indent=4) 

259 

260 See the appropriate encoder's documentation for details about 

261 the supported arguments and keyword arguments. 

262 

263 WARNING: If you pass sort_keys=True, and the object to encode 

264 contains ``__slots__``, and you set ``warn`` to True, 

265 a TypeError will be raised! 

266 """ 

267 self._encoder_options[name] = (args, kwargs) 

268 

269 def set_decoder_options(self, name, *args, **kwargs): 

270 """ 

271 Associate decoder-specific options with a decoder. 

272 

273 After calling set_decoder_options, any calls to jsonpickle's 

274 decode method will pass the supplied args and kwargs along to 

275 the appropriate backend's decode method. 

276 

277 For example:: 

278 

279 set_decoder_options('simplejson', encoding='utf8', cls=JSONDecoder) 

280 

281 See the appropriate decoder's documentation for details about 

282 the supported arguments and keyword arguments. 

283 

284 """ 

285 self._decoder_options[name] = (args, kwargs) 

286 

287 

288json = JSONBackend()