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
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:20 +0000
1from __future__ import absolute_import, division, unicode_literals
3from .compat import string_types
6class JSONBackend(object):
7 """Manages encoding and decoding using various backends.
9 It tries these modules in this order:
10 simplejson, json, ujson
12 simplejson is a fast and popular backend and is tried first.
13 json comes with Python and is tried second.
15 """
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 )
27 def encode(self, obj, indent=None, separators=None):
28 """
29 Attempt to encode an object into JSON.
31 This tries the loaded backends in order and passes along the last
32 exception if no backend is able to encode the object.
34 """
35 self._verify()
37 if not self._fallthrough:
38 name = self._backend_names[0]
39 return self.backend_encode(name, obj, indent=indent, separators=separators)
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
50 # def dumps
51 dumps = encode
53 def decode(self, string):
54 """
55 Attempt to decode an object from a JSON string.
57 This tries the loaded backends in order and passes along the last
58 exception if no backends are able to decode the string.
60 """
61 self._verify()
63 if not self._fallthrough:
64 name = self._backend_names[0]
65 return self.backend_decode(name, string)
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
76 # def loads
77 loads = decode
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 = []
85 # A dictionary mapping backend names to encode/decode functions
86 self._encoders = {}
87 self._decoders = {}
89 # Options to pass to specific encoders
90 self._encoder_options = {}
92 # Options to pass to specific decoders
93 self._decoder_options = {}
95 # The exception class that is thrown when a decoding error occurs
96 self._decoder_exceptions = {}
98 # Whether we've loaded any backends successfully
99 self._verified = False
101 self.load_backend('simplejson')
102 self.load_backend('json')
103 self.load_backend('ujson')
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 }
114 def enable_fallthrough(self, enable):
115 """
116 Disable jsonpickle's fallthrough-on-error behavior
118 By default, jsonpickle tries the next backend when decoding or
119 encoding using a backend fails.
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.
125 Calling `enable_backend(False)` will make jsonpickle immediately
126 re-raise any exceptions raised by the backends.
128 """
129 self._fallthrough = enable
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
139 def load_backend(self, name, dumps='dumps', loads='loads', loads_exc=ValueError):
140 """Load a JSON backend by name.
142 This method loads a backend and sets up references to that
143 backend's loads/dumps functions and exception classes.
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.
159 """
160 try:
161 # Load the JSON backend
162 mod = __import__(name)
163 except ImportError:
164 return False
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
173 if not self._store(self._encoders, name, mod, dumps) or not self._store(
174 self._decoders, name, mod, loads
175 ):
176 return False
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
186 # Setup the default args and kwargs for this encoder/decoder
187 self._encoder_options.setdefault(name, ([], {}))
188 self._decoder_options.setdefault(name, ([], {}))
190 # Add this backend to the list of candidate backends
191 self._backend_names.append(name)
193 # Indicate that we successfully loaded a JSON backend
194 self._verified = True
195 return True
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)
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)
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)
223 def set_preferred_backend(self, name):
224 """
225 Set the preferred json backend.
227 If a preferred backend is set then jsonpickle tries to use it
228 before any other backend.
230 For example::
232 set_preferred_backend('simplejson')
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.
238 AssertionError is raised if the backend has not been loaded.
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)
248 def set_encoder_options(self, name, *args, **kwargs):
249 """
250 Associate encoder-specific options with an encoder.
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.
256 For example::
258 set_encoder_options('simplejson', sort_keys=True, indent=4)
260 See the appropriate encoder's documentation for details about
261 the supported arguments and keyword arguments.
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)
269 def set_decoder_options(self, name, *args, **kwargs):
270 """
271 Associate decoder-specific options with a decoder.
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.
277 For example::
279 set_decoder_options('simplejson', encoding='utf8', cls=JSONDecoder)
281 See the appropriate decoder's documentation for details about
282 the supported arguments and keyword arguments.
284 """
285 self._decoder_options[name] = (args, kwargs)
288json = JSONBackend()