1# Copyright (C) 2008 John Paulett (john -at- paulett.org)
2# Copyright (C) 2009-2024 David Aguilar (davvid -at- gmail.com)
3# All rights reserved.
4#
5# This software is licensed as described in the file COPYING, which
6# you should have received as part of this distribution.
7import dataclasses
8import sys
9import warnings
10from types import ModuleType
11from typing import (
12 Any,
13 Callable,
14 Dict,
15 Iterator,
16 List,
17 Optional,
18 Sequence,
19 Set,
20 Tuple,
21 Type,
22 Union,
23)
24
25from . import errors, handlers, tags, util
26from .backend import JSONBackend, json
27
28# class names to class objects (or sequence of classes)
29ClassesType = Optional[Union[Type[Any], Dict[str, Type[Any]], Sequence[Type[Any]]]]
30# handler for missing classes: either a policy name or a callback
31MissingHandler = Union[str, Callable[[str], Any]]
32
33
34def decode(
35 string: str,
36 backend: Optional[JSONBackend] = None,
37 # we get a lot of errors when typing with TypeVar
38 context: Optional["Unpickler"] = None,
39 keys: bool = False,
40 reset: bool = True,
41 safe: bool = True,
42 classes: Optional[ClassesType] = None,
43 v1_decode: bool = False,
44 on_missing: MissingHandler = 'ignore',
45 handle_readonly: bool = False,
46) -> Any:
47 """Convert a JSON string into a Python object.
48
49 :param backend: If set to an instance of jsonpickle.backend.JSONBackend, jsonpickle
50 will use that backend for deserialization.
51
52 :param context: Supply a pre-built Pickler or Unpickler object to the
53 `jsonpickle.encode` and `jsonpickle.decode` machinery instead
54 of creating a new instance. The `context` represents the currently
55 active Pickler and Unpickler objects when custom handlers are
56 invoked by jsonpickle.
57
58 :param keys: If set to True then jsonpickle will decode non-string dictionary keys
59 into python objects via the jsonpickle protocol.
60
61 :param reset: Custom pickle handlers that use the `Pickler.flatten` method or
62 `jsonpickle.encode` function must call `encode` with `reset=False`
63 in order to retain object references during pickling.
64 This flag is not typically used outside of a custom handler or
65 `__getstate__` implementation.
66
67 :param safe: If set to ``False``, use of ``eval()`` for backwards-compatible (pre-0.7.0)
68 deserialization of repr-serialized objects is enabled. Defaults to ``True``.
69 The default value was ``False`` in jsonpickle v3 and changed to ``True`` in jsonpickle v4.
70
71 .. warning::
72
73 ``eval()`` is used when set to ``False`` and is not secure against
74 malicious inputs. You should avoid setting ``safe=False``.
75
76 :param classes: If set to a single class, or a sequence (list, set, tuple) of
77 classes, then the classes will be made available when constructing objects.
78 If set to a dictionary of class names to class objects, the class object
79 will be provided to jsonpickle to deserialize the class name into.
80 This can be used to give jsonpickle access to local classes that are not
81 available through the global module import scope, and the dict method can
82 be used to deserialize encoded objects into a new class.
83
84 :param v1_decode: If set to True it enables you to decode objects serialized in
85 jsonpickle v1. Please do not attempt to re-encode the objects in the v1 format!
86 Version 2's format fixes issue #255, and allows dictionary identity to be
87 preserved through an encode/decode cycle.
88
89 :param on_missing: If set to 'error', it will raise an error if the class it's
90 decoding is not found. If set to 'warn', it will warn you in said case.
91 If set to a non-awaitable function, it will call said callback function
92 with the class name (a string) as the only parameter. Strings passed to
93 `on_missing` are lowercased automatically.
94
95 :param handle_readonly: If set to True, the Unpickler will handle objects encoded
96 with 'handle_readonly' properly. Do not set this flag for objects not encoded
97 with 'handle_readonly' set to True.
98
99
100 >>> decode('"my string"') == 'my string'
101 True
102 >>> decode('36')
103 36
104 """
105
106 if isinstance(on_missing, str):
107 on_missing = on_missing.lower()
108 elif not util._is_function(on_missing):
109 warnings.warn(
110 "Unpickler.on_missing must be a string or a function! It will be ignored!"
111 )
112
113 backend = backend or json
114 context = context or Unpickler(
115 keys=keys,
116 backend=backend,
117 safe=safe,
118 v1_decode=v1_decode,
119 on_missing=on_missing,
120 handle_readonly=handle_readonly,
121 )
122 data = backend.decode(string)
123 return context.restore(data, reset=reset, classes=classes)
124
125
126def _safe_hasattr(obj: Any, attr: str) -> bool:
127 """Workaround unreliable hasattr() availability on sqlalchemy objects"""
128 try:
129 object.__getattribute__(obj, attr)
130 return True
131 except AttributeError:
132 return False
133
134
135def _is_json_key(key: Any) -> bool:
136 """Has this key a special object that has been encoded to JSON?"""
137 return isinstance(key, str) and key.startswith(tags.JSON_KEY)
138
139
140class _Proxy:
141 """Proxies are dummy objects that are later replaced by real instances
142
143 The `restore()` function has to solve a tricky problem when pickling
144 objects with cyclical references -- the parent instance does not yet
145 exist.
146
147 The problem is that `__getnewargs__()`, `__getstate__()`, custom handlers,
148 and cyclical objects graphs are allowed to reference the yet-to-be-created
149 object via the referencing machinery.
150
151 In other words, objects are allowed to depend on themselves for
152 construction!
153
154 We solve this problem by placing dummy Proxy objects into the referencing
155 machinery so that we can construct the child objects before constructing
156 the parent. Objects are initially created with Proxy attribute values
157 instead of real references.
158
159 We collect all objects that contain references to proxies and run
160 a final sweep over them to swap in the real instance. This is done
161 at the very end of the top-level `restore()`.
162
163 The `instance` attribute below is replaced with the real instance
164 after `__new__()` has been used to construct the object and is used
165 when swapping proxies with real instances.
166
167 """
168
169 def __init__(self) -> None:
170 self.instance = None
171
172 def get(self) -> Any:
173 return self.instance
174
175 def reset(self, instance: Any) -> None:
176 self.instance = instance
177
178
179class _IDProxy(_Proxy):
180 def __init__(self, objs: List[Any], index: int) -> None:
181 self._index = index
182 self._objs = objs
183
184 def get(self) -> Any:
185 try:
186 return self._objs[self._index]
187 except IndexError:
188 return None
189
190
191def _obj_setattr(obj: Any, attr: str, proxy: _Proxy) -> None:
192 """Use setattr to update a proxy entry"""
193 setattr(obj, attr, proxy.get())
194
195
196def _obj_setvalue(obj: Any, idx: Any, proxy: _Proxy) -> None:
197 """Use obj[key] assignments to update a proxy entry"""
198 obj[idx] = proxy.get()
199
200
201def loadclass(
202 module_and_name: str, classes: Optional[Dict[str, Type[Any]]] = None
203) -> Optional[Union[Type[Any], ModuleType]]:
204 """Loads the module and returns the class.
205
206 >>> cls = loadclass('datetime.datetime')
207 >>> cls.__name__
208 'datetime'
209
210 >>> loadclass('does.not.exist')
211
212 >>> loadclass('builtins.int')()
213 0
214
215 """
216 # Check if the class exists in a caller-provided scope
217 if classes:
218 try:
219 return classes[module_and_name]
220 except KeyError:
221 # maybe they didn't provide a fully qualified path
222 try:
223 return classes[module_and_name.rsplit('.', 1)[-1]]
224 except KeyError:
225 pass
226 # Otherwise, load classes from globally-accessible imports
227 names = module_and_name.split('.')
228 # First assume that everything up to the last dot is the module name,
229 # then try other splits to handle classes that are defined within
230 # classes
231 for up_to in range(len(names) - 1, 0, -1):
232 module = util.untranslate_module_name('.'.join(names[:up_to]))
233 try:
234 __import__(module)
235 obj = sys.modules[module]
236 for class_name in names[up_to:]:
237 obj = getattr(obj, class_name)
238 return obj
239 except (AttributeError, ImportError, ValueError):
240 continue
241 # NoneType is a special case and can not be imported/created
242 if module_and_name == "builtins.NoneType":
243 return type(None)
244 return None
245
246
247def has_tag(obj: Any, tag: str) -> bool:
248 """Helper class that tests to see if the obj is a dictionary
249 and contains a particular key/tag.
250
251 >>> obj = {'test': 1}
252 >>> has_tag(obj, 'test')
253 True
254 >>> has_tag(obj, 'fail')
255 False
256
257 >>> has_tag(42, 'fail')
258 False
259
260 """
261 return type(obj) is dict and tag in obj
262
263
264def getargs(
265 obj: Dict[str, Any], classes: Optional[Dict[str, Type[Any]]] = None
266) -> List[Any]:
267 """Return arguments suitable for __new__()"""
268 # Let saved newargs take precedence over everything
269 if has_tag(obj, tags.NEWARGSEX):
270 raise ValueError('__newargs_ex__ returns both args and kwargs')
271
272 if has_tag(obj, tags.NEWARGS):
273 return obj[tags.NEWARGS]
274
275 if has_tag(obj, tags.INITARGS):
276 return obj[tags.INITARGS]
277
278 try:
279 seq_list = obj[tags.SEQ]
280 obj_dict = obj[tags.OBJECT]
281 except KeyError:
282 return []
283 typeref = loadclass(obj_dict, classes=classes)
284 if not typeref:
285 return []
286 if hasattr(typeref, '_fields'):
287 if len(typeref._fields) == len(seq_list):
288 return seq_list
289 return []
290
291
292class _trivialclassic:
293 """
294 A trivial class that can be instantiated with no args
295 """
296
297
298def make_blank_classic(cls: Type[Any]) -> _trivialclassic:
299 """
300 Implement the mandated strategy for dealing with classic classes
301 which cannot be instantiated without __getinitargs__ because they
302 take parameters
303 """
304 instance = _trivialclassic()
305 instance.__class__ = cls
306 return instance
307
308
309def loadrepr(reprstr: str) -> Any:
310 """Returns an instance of the object from the object's repr() string.
311 It involves the dynamic specification of code.
312
313 .. warning::
314
315 This function is unsafe and uses `eval()`.
316
317 >>> obj = loadrepr('datetime/datetime.datetime.now()')
318 >>> obj.__class__.__name__
319 'datetime'
320
321 """
322 module, evalstr = reprstr.split('/')
323 mylocals = locals()
324 localname = module
325 if '.' in localname:
326 localname = module.split('.', 1)[0]
327 mylocals[localname] = __import__(module)
328 return eval(evalstr, mylocals)
329
330
331def _loadmodule(module_str: str) -> Optional[Any]:
332 """Returns a reference to a module.
333
334 >>> fn = _loadmodule('datetime/datetime.datetime.fromtimestamp')
335 >>> fn.__name__
336 'fromtimestamp'
337
338 """
339 module, identifier = module_str.split('/')
340 try:
341 result = __import__(module)
342 except ImportError:
343 return None
344 identifier_parts = identifier.split('.')
345 first_identifier = identifier_parts[0]
346 if first_identifier != module and not module.startswith(f'{first_identifier}.'):
347 return None
348 for name in identifier_parts[1:]:
349 try:
350 result = getattr(result, name)
351 except AttributeError:
352 return None
353 return result
354
355
356def has_tag_dict(obj: Any, tag: str) -> bool:
357 """Helper class that tests to see if the obj is a dictionary
358 and contains a particular key/tag.
359
360 >>> obj = {'test': 1}
361 >>> has_tag(obj, 'test')
362 True
363 >>> has_tag(obj, 'fail')
364 False
365
366 >>> has_tag(42, 'fail')
367 False
368
369 """
370 return tag in obj
371
372
373def _passthrough(value: Any) -> Any:
374 """A function that returns its input as-is"""
375 return value
376
377
378class Unpickler:
379 def __init__(
380 self,
381 backend: Optional[JSONBackend] = None,
382 keys: bool = False,
383 safe: bool = True,
384 v1_decode: bool = False,
385 on_missing: MissingHandler = 'ignore',
386 handle_readonly: bool = False,
387 ) -> None:
388 self.backend = backend or json
389 self.keys = keys
390 self.safe = safe
391 self.v1_decode = v1_decode
392 self.on_missing = on_missing
393 self.handle_readonly = handle_readonly
394
395 self.reset()
396
397 def reset(self) -> None:
398 """Resets the object's internal state."""
399 # Map reference names to object instances
400 self._namedict = {}
401
402 # The stack of names traversed for child objects
403 self._namestack = []
404
405 # Map of objects to their index in the _objs list
406 self._obj_to_idx = {}
407 self._objs = []
408 self._proxies = []
409
410 # Extra local classes not accessible globally
411 self._classes = {}
412
413 def _swap_proxies(self) -> None:
414 """Replace proxies with their corresponding instances"""
415 for obj, attr, proxy, method in self._proxies:
416 method(obj, attr, proxy)
417 self._proxies = []
418
419 def _restore(
420 self, obj: Any, _passthrough: Callable[[Any], Any] = _passthrough
421 ) -> Any:
422 # if obj isn't in these types, neither it nor nothing in it can have a tag
423 # don't change the tuple of types to a set, it won't work with isinstance
424 if not isinstance(obj, (str, list, dict, set, tuple)):
425 restore = _passthrough
426 else:
427 restore = self._restore_tags(obj)
428 return restore(obj)
429
430 def restore(
431 self, obj: Any, reset: bool = True, classes: Optional[ClassesType] = None
432 ) -> Any:
433 """Restores a flattened object to its original python state.
434
435 Simply returns any of the basic builtin types
436
437 >>> u = Unpickler()
438 >>> u.restore('hello world') == 'hello world'
439 True
440 >>> u.restore({'key': 'value'}) == {'key': 'value'}
441 True
442
443 """
444 if reset:
445 self.reset()
446 if classes:
447 self.register_classes(classes)
448 value = self._restore(obj)
449 if reset:
450 self._swap_proxies()
451 return value
452
453 def register_classes(self, classes: ClassesType) -> None:
454 """Register one or more classes
455
456 :param classes: sequence of classes or a single class to register
457
458 """
459 if isinstance(classes, (list, tuple, set)):
460 for cls in classes:
461 self.register_classes(cls)
462 elif isinstance(classes, dict):
463 self._classes.update(
464 (
465 cls if isinstance(cls, str) else util.importable_name(cls),
466 handler,
467 )
468 for cls, handler in classes.items()
469 )
470 else:
471 self._classes[util.importable_name(classes)] = classes # type: ignore[arg-type]
472
473 def _restore_base64(self, obj: Dict[str, Any]) -> bytes:
474 try:
475 return util.b64decode(obj[tags.B64].encode('utf-8'))
476 except (AttributeError, UnicodeEncodeError):
477 return b''
478
479 def _restore_base85(self, obj: Dict[str, Any]) -> bytes:
480 try:
481 return util.b85decode(obj[tags.B85].encode('utf-8'))
482 except (AttributeError, UnicodeEncodeError):
483 return b''
484
485 def _refname(self) -> str:
486 """Calculates the name of the current location in the JSON stack.
487
488 This is called as jsonpickle traverses the object structure to
489 create references to previously-traversed objects. This allows
490 cyclical data structures such as doubly-linked lists.
491 jsonpickle ensures that duplicate python references to the same
492 object results in only a single JSON object definition and
493 special reference tags to represent each reference.
494
495 >>> u = Unpickler()
496 >>> u._namestack = []
497 >>> u._refname() == '/'
498 True
499 >>> u._namestack = ['a']
500 >>> u._refname() == '/a'
501 True
502 >>> u._namestack = ['a', 'b']
503 >>> u._refname() == '/a/b'
504 True
505
506 """
507 return '/' + '/'.join(self._namestack)
508
509 def _mkref(self, obj: Any) -> Any:
510 obj_id = id(obj)
511 try:
512 _ = self._obj_to_idx[obj_id]
513 except KeyError:
514 self._obj_to_idx[obj_id] = len(self._objs)
515 self._objs.append(obj)
516 # Backwards compatibility: old versions of jsonpickle
517 # produced "py/ref" references.
518 self._namedict[self._refname()] = obj
519 return obj
520
521 def _restore_list(self, obj: List[Any]) -> List[Any]:
522 parent = []
523 self._mkref(parent)
524 children = [self._restore(v) for v in obj]
525 parent.extend(children)
526 method = _obj_setvalue
527 proxies = [
528 (parent, idx, value, method)
529 for idx, value in enumerate(parent)
530 if isinstance(value, _Proxy)
531 ]
532 self._proxies.extend(proxies)
533 return parent
534
535 def _restore_iterator(self, obj: Dict[str, Any]) -> Iterator[Any]:
536 try:
537 return iter(self._restore_list(obj[tags.ITERATOR]))
538 except TypeError:
539 return iter([])
540
541 def _swapref(self, proxy: _Proxy, instance: Any) -> None:
542 proxy_id = id(proxy)
543 instance_id = id(instance)
544
545 instance_index = self._obj_to_idx[proxy_id]
546 self._obj_to_idx[instance_id] = instance_index
547 del self._obj_to_idx[proxy_id]
548
549 self._objs[instance_index] = instance
550 self._namedict[self._refname()] = instance
551
552 def _restore_reduce(self, obj: Dict[str, Any]) -> Any:
553 """
554 Supports restoring with all elements of __reduce__ as per pep 307.
555 Assumes that iterator items (the last two) are represented as lists
556 as per pickler implementation.
557 """
558 proxy = _Proxy()
559 self._mkref(proxy)
560 try:
561 reduce_val = list(map(self._restore, obj[tags.REDUCE]))
562 except TypeError:
563 result = []
564 proxy.reset(result)
565 self._swapref(proxy, result)
566 return result
567 if len(reduce_val) < 5:
568 reduce_val.extend([None] * (5 - len(reduce_val)))
569 f, args, state, listitems, dictitems = reduce_val
570
571 if f == tags.NEWOBJ or getattr(f, '__name__', '') == '__newobj__':
572 # mandated special case
573 cls = args[0]
574 if not isinstance(cls, type):
575 cls = self._restore(cls)
576 stage1 = cls.__new__(cls, *args[1:])
577 else:
578 if not callable(f):
579 result = []
580 proxy.reset(result)
581 self._swapref(proxy, result)
582 return result
583 try:
584 stage1 = f(*args)
585 except TypeError:
586 # this happens when there are missing kwargs and args don't match so we bypass
587 # __init__ since the state dict will set all attributes immediately afterwards
588 stage1 = f.__new__(f, *args)
589
590 if state:
591 try:
592 stage1.__setstate__(state)
593 except AttributeError:
594 # it's fine - we'll try the prescribed default methods
595 try:
596 # we can't do a straight update here because we
597 # need object identity of the state dict to be
598 # preserved so that _swap_proxies works out
599 for k, v in stage1.__dict__.items():
600 state.setdefault(k, v)
601 stage1.__dict__ = state
602 except AttributeError:
603 # next prescribed default
604 try:
605 for k, v in state.items():
606 setattr(stage1, k, v)
607 except Exception:
608 dict_state, slots_state = state
609 if dict_state:
610 stage1.__dict__.update(dict_state)
611 if slots_state:
612 for k, v in slots_state.items():
613 setattr(stage1, k, v)
614
615 if listitems:
616 # should be lists if not None
617 try:
618 stage1.extend(listitems)
619 except AttributeError:
620 for x in listitems:
621 stage1.append(x)
622
623 if dictitems:
624 for k, v in dictitems:
625 stage1.__setitem__(k, v)
626
627 proxy.reset(stage1)
628 self._swapref(proxy, stage1)
629 return stage1
630
631 def _restore_id(self, obj: Dict[str, Any]) -> Any:
632 try:
633 idx = obj[tags.ID]
634 return self._objs[idx]
635 except IndexError:
636 return _IDProxy(self._objs, idx)
637 except TypeError:
638 return None
639
640 def _restore_type(self, obj: Dict[str, Any]) -> Any:
641 typeref = loadclass(obj[tags.TYPE], classes=self._classes)
642 if typeref is None:
643 return obj
644 return typeref
645
646 def _restore_module(self, obj: Dict[str, Any]) -> Any:
647 new_obj = _loadmodule(obj[tags.MODULE])
648 return self._mkref(new_obj)
649
650 def _restore_repr_safe(self, obj: Dict[str, Any]) -> Any:
651 new_obj = _loadmodule(obj[tags.REPR])
652 return self._mkref(new_obj)
653
654 def _restore_repr(self, obj: Dict[str, Any]) -> Any:
655 obj = loadrepr(obj[tags.REPR])
656 return self._mkref(obj)
657
658 def _loadfactory(self, obj: Dict[str, Any]) -> Optional[Any]:
659 try:
660 default_factory = obj['default_factory']
661 except KeyError:
662 return None
663 del obj['default_factory']
664 return self._restore(default_factory)
665
666 def _process_missing(self, class_name: str) -> None:
667 # most common case comes first
668 if self.on_missing == 'ignore':
669 pass
670 elif self.on_missing == 'warn':
671 warnings.warn('Unpickler._restore_object could not find %s!' % class_name)
672 elif self.on_missing == 'error':
673 raise errors.ClassNotFoundError(
674 'Unpickler.restore_object could not find %s!' % class_name # type: ignore[arg-type]
675 )
676 elif util._is_function(self.on_missing):
677 self.on_missing(class_name) # type: ignore[operator]
678
679 def _restore_pickled_key(self, key: str) -> Any:
680 """Restore a possibly pickled key"""
681 if _is_json_key(key):
682 key = decode(
683 key[len(tags.JSON_KEY) :],
684 backend=self.backend,
685 context=self,
686 keys=True,
687 reset=False,
688 )
689 return key
690
691 def _restore_key_fn(
692 self, _passthrough: Callable[[Any], Any] = _passthrough
693 ) -> Callable[[Any], Any]:
694 """Return a callable that restores keys
695
696 This function is responsible for restoring non-string keys
697 when we are decoding with `keys=True`.
698
699 """
700 # This function is called before entering a tight loop
701 # where the returned function will be called.
702 # We return a specific function after checking self.keys
703 # instead of doing so in the body of the function to
704 # avoid conditional branching inside a tight loop.
705 if self.keys:
706 restore_key = self._restore_pickled_key
707 else:
708 restore_key = _passthrough # type: ignore[assignment]
709 return restore_key
710
711 def _restore_from_dict(
712 self,
713 obj: Dict[str, Any],
714 instance: Any,
715 ignorereserved: bool = True,
716 restore_dict_items: bool = True,
717 ) -> Any:
718 restore_key = self._restore_key_fn()
719 method = _obj_setattr
720 deferred = {}
721
722 for k, v in util.items(obj):
723 # ignore the reserved attribute
724 if ignorereserved and k in tags.RESERVED:
725 continue
726 if isinstance(k, (int, float)):
727 str_k = k.__str__()
728 else:
729 str_k = k
730 self._namestack.append(str_k)
731 if restore_dict_items:
732 k = restore_key(k)
733 # step into the namespace
734 value = self._restore(v)
735 else:
736 value = v
737 if util._is_noncomplex(instance) or util._is_dictionary_subclass(instance):
738 try:
739 if k == '__dict__':
740 setattr(instance, k, value)
741 else:
742 instance[k] = value
743 except TypeError:
744 # Immutable object, must be constructed in one shot
745 if k != '__dict__':
746 deferred[k] = value
747 self._namestack.pop()
748 continue
749 else:
750 if not k.startswith('__'):
751 try:
752 setattr(instance, k, value)
753 except KeyError:
754 # certain numpy objects require us to prepend a _ to the var
755 # this should go in the np handler but I think this could be
756 # useful for other code
757 setattr(instance, f'_{k}', value)
758 except dataclasses.FrozenInstanceError:
759 # issue #240
760 # i think this is the only way to set frozen dataclass attrs
761 object.__setattr__(instance, k, value)
762 except AttributeError as e:
763 # some objects raise this for read-only attributes (#422) (#478)
764 if (
765 hasattr(instance, '__slots__')
766 and not len(instance.__slots__)
767 # we have to handle this separately because of +483
768 and issubclass(instance.__class__, (int, str))
769 and self.handle_readonly
770 ):
771 continue
772 raise e
773 else:
774 setattr(instance, f'_{instance.__class__.__name__}{k}', value)
775
776 # This instance has an instance variable named `k` that is
777 # currently a proxy and must be replaced
778 if isinstance(value, _Proxy):
779 self._proxies.append((instance, k, value, method))
780
781 # step out
782 self._namestack.pop()
783
784 if deferred:
785 # SQLAlchemy Immutable mappings must be constructed in one shot
786 instance = instance.__class__(deferred)
787
788 return instance
789
790 def _restore_state(self, obj: Dict[str, Any], instance: Any) -> Any:
791 state = self._restore(obj[tags.STATE])
792 has_slots = (
793 isinstance(state, tuple) and len(state) == 2 and isinstance(state[1], dict)
794 )
795 has_slots_and_dict = has_slots and isinstance(state[0], dict)
796 if hasattr(instance, '__setstate__'):
797 instance.__setstate__(state)
798 elif isinstance(state, dict):
799 # implements described default handling
800 # of state for object with instance dict
801 # and no slots
802 instance = self._restore_from_dict(
803 state, instance, ignorereserved=False, restore_dict_items=False
804 )
805 elif has_slots:
806 instance = self._restore_from_dict(
807 state[1], instance, ignorereserved=False, restore_dict_items=False
808 )
809 if has_slots_and_dict:
810 instance = self._restore_from_dict(
811 state[0], instance, ignorereserved=False, restore_dict_items=False
812 )
813 elif not hasattr(instance, '__getnewargs__') and not hasattr(
814 instance, '__getnewargs_ex__'
815 ):
816 # __setstate__ is not implemented so that means that the best
817 # we can do is return the result of __getstate__() rather than
818 # return an empty shell of an object.
819 # However, if there were newargs, it's not an empty shell
820 instance = state
821 return instance
822
823 def _restore_object_instance_variables(
824 self, obj: Dict[str, Any], instance: Any
825 ) -> Any:
826 instance = self._restore_from_dict(obj, instance)
827
828 # Handle list and set subclasses
829 if has_tag(obj, tags.SEQ):
830 if hasattr(instance, 'append'):
831 for v in obj[tags.SEQ]:
832 instance.append(self._restore(v))
833 elif hasattr(instance, 'add'):
834 for v in obj[tags.SEQ]:
835 instance.add(self._restore(v))
836
837 if has_tag(obj, tags.STATE):
838 instance = self._restore_state(obj, instance)
839
840 return instance
841
842 def _restore_object_instance(
843 self, obj: Dict[str, Any], cls: Type[Any], class_name: str = ''
844 ) -> Any:
845 # This is a placeholder proxy object which allows child objects to
846 # reference the parent object before it has been instantiated.
847 proxy = _Proxy()
848 self._mkref(proxy)
849
850 # An object can install itself as its own factory, so load the factory
851 # after the instance is available for referencing.
852 factory = self._loadfactory(obj)
853
854 if has_tag(obj, tags.NEWARGSEX):
855 args, kwargs = obj[tags.NEWARGSEX]
856 else:
857 args = getargs(obj, classes=self._classes)
858 kwargs = {}
859 if args:
860 args = self._restore(args)
861 if kwargs:
862 kwargs = self._restore(kwargs)
863
864 is_oldstyle = not (isinstance(cls, type) or getattr(cls, '__meta__', None))
865 try:
866 if not is_oldstyle and hasattr(cls, '__new__'):
867 # new style classes
868 if factory:
869 instance = cls.__new__(cls, factory, *args, **kwargs)
870 instance.default_factory = factory
871 else:
872 instance = cls.__new__(cls, *args, **kwargs)
873 else:
874 instance = object.__new__(cls)
875 except TypeError: # old-style classes
876 is_oldstyle = True
877
878 if is_oldstyle:
879 try:
880 instance = cls(*args)
881 except TypeError: # fail gracefully
882 try:
883 instance = make_blank_classic(cls)
884 except Exception: # fail gracefully
885 self._process_missing(class_name)
886 return self._mkref(obj)
887
888 proxy.reset(instance)
889 self._swapref(proxy, instance)
890
891 if isinstance(instance, tuple):
892 return instance
893
894 instance = self._restore_object_instance_variables(obj, instance)
895
896 if _safe_hasattr(instance, 'default_factory') and isinstance(
897 instance.default_factory, _Proxy
898 ):
899 instance.default_factory = instance.default_factory.get()
900
901 return instance
902
903 def _restore_object(self, obj: Dict[str, Any]) -> Any:
904 class_name = obj[tags.OBJECT]
905 cls = loadclass(class_name, classes=self._classes)
906 handler = handlers.get(cls, handlers.get(class_name)) # type: ignore[arg-type]
907 if handler is not None: # custom handler
908 proxy = _Proxy()
909 self._mkref(proxy)
910 instance = handler(self).restore(obj)
911 proxy.reset(instance)
912 self._swapref(proxy, instance)
913 return instance
914
915 if cls is None:
916 self._process_missing(class_name)
917 return self._mkref(obj)
918
919 return self._restore_object_instance(obj, cls, class_name) # type: ignore[arg-type]
920
921 def _restore_function(self, obj: Dict[str, Any]) -> Any:
922 return loadclass(obj[tags.FUNCTION], classes=self._classes)
923
924 def _restore_set(self, obj: Dict[str, Any]) -> Set[Any]:
925 try:
926 return {self._restore(v) for v in obj[tags.SET]}
927 except TypeError:
928 return set()
929
930 def _restore_dict(self, obj: Dict[str, Any]) -> Dict[str, Any]:
931 data = {}
932 if not self.v1_decode:
933 self._mkref(data)
934
935 # If we are decoding dicts that can have non-string keys then we
936 # need to do a two-phase decode where the non-string keys are
937 # processed last. This ensures a deterministic order when
938 # assigning object IDs for references.
939 if self.keys:
940 # Phase 1: regular non-special keys.
941 for k, v in util.items(obj):
942 if _is_json_key(k):
943 continue
944 if isinstance(k, (int, float)):
945 str_k = k.__str__()
946 else:
947 str_k = k
948 self._namestack.append(str_k)
949 data[k] = self._restore(v)
950
951 self._namestack.pop()
952
953 # Phase 2: object keys only.
954 for k, v in util.items(obj):
955 if not _is_json_key(k):
956 continue
957 self._namestack.append(k)
958
959 k = self._restore_pickled_key(k)
960 data[k] = result = self._restore(v)
961 # k is currently a proxy and must be replaced
962 if isinstance(result, _Proxy):
963 self._proxies.append((data, k, result, _obj_setvalue))
964
965 self._namestack.pop()
966 else:
967 # No special keys, thus we don't need to restore the keys either.
968 for k, v in util.items(obj):
969 if isinstance(k, (int, float)):
970 str_k = k.__str__()
971 else:
972 str_k = k
973 self._namestack.append(str_k)
974 data[k] = result = self._restore(v)
975 if isinstance(result, _Proxy):
976 self._proxies.append((data, k, result, _obj_setvalue))
977 self._namestack.pop()
978 return data
979
980 def _restore_tuple(self, obj: Dict[str, Any]) -> Tuple[Any, ...]:
981 try:
982 return tuple(self._restore(v) for v in obj[tags.TUPLE])
983 except TypeError:
984 return ()
985
986 def _restore_tags(
987 self, obj: Any, _passthrough: Callable[[Any], Any] = _passthrough
988 ) -> Callable[[Any], Any]:
989 """Return the restoration function for the specified object"""
990 try:
991 if not tags.RESERVED <= set(obj) and type(obj) not in (list, dict):
992 return _passthrough
993 except TypeError:
994 pass
995 if type(obj) is dict:
996 if tags.TUPLE in obj:
997 restore = self._restore_tuple
998 elif tags.SET in obj:
999 restore = self._restore_set # type: ignore[assignment]
1000 elif tags.B64 in obj:
1001 restore = self._restore_base64 # type: ignore[assignment]
1002 elif tags.B85 in obj:
1003 restore = self._restore_base85 # type: ignore[assignment]
1004 elif tags.ID in obj:
1005 restore = self._restore_id
1006 elif tags.ITERATOR in obj:
1007 restore = self._restore_iterator # type: ignore[assignment]
1008 elif tags.OBJECT in obj:
1009 restore = self._restore_object
1010 elif tags.TYPE in obj:
1011 restore = self._restore_type
1012 elif tags.REDUCE in obj:
1013 restore = self._restore_reduce
1014 elif tags.FUNCTION in obj:
1015 restore = self._restore_function
1016 elif tags.MODULE in obj:
1017 restore = self._restore_module
1018 elif tags.REPR in obj:
1019 if self.safe:
1020 restore = self._restore_repr_safe
1021 else:
1022 restore = self._restore_repr
1023 else:
1024 restore = self._restore_dict # type: ignore[assignment]
1025 elif type(obj) is list:
1026 restore = self._restore_list # type: ignore[assignment]
1027 else:
1028 restore = _passthrough # type: ignore[assignment]
1029 return restore