Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jsonpickle/unpickler.py: 71%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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 warnings
9from collections.abc import Callable, Iterator, Sequence
10from typing import Any, TypeAlias
12from . import errors, handlers, tags, util
13from .backend import json
15# class names to class objects (or sequence of classes)
16ClassesType: TypeAlias = type | dict[str, type] | Sequence[type] | None
17# handler for missing classes: either a policy name or a callback
18MissingHandler: TypeAlias = str | Callable[[str], Any]
21def decode(
22 string: str,
23 # we get a lot of errors when typing with TypeVar
24 context: "Unpickler | None" = None,
25 keys: bool = True,
26 reset: bool = True,
27 safe: bool = True,
28 classes: ClassesType | None = None,
29 on_missing: MissingHandler = "ignore",
30 handle_readonly: bool = False,
31 handler_context: Any = None,
32) -> Any:
33 """Convert a JSON string into a Python object.
35 :param context: Supply a pre-built Pickler or Unpickler object to the
36 `jsonpickle.encode` and `jsonpickle.decode` machinery instead
37 of creating a new instance. The `context` represents the currently
38 active Pickler and Unpickler objects when custom handlers are
39 invoked by jsonpickle.
41 :param keys: If set to True, the default, then jsonpickle will decode
42 non-string dictionary keys into python objects via the jsonpickle
43 protocol. Otherwise, jsonpickle will decode those keys as strings.
45 :param reset: Custom pickle handlers that use the `Pickler.flatten` method or
46 `jsonpickle.encode` function must call `encode` with `reset=False`
47 in order to retain object references during pickling.
48 This flag is not typically used outside of a custom handler or
49 `__getstate__` implementation.
51 :param safe: If set to ``False``, use of ``eval()`` for backwards-compatible (pre-0.7.0)
52 deserialization of repr-serialized objects is enabled. Defaults to ``True``.
53 The default value was ``False`` in jsonpickle v3 and changed to ``True`` in jsonpickle v4.
55 .. warning::
57 ``eval()`` is used when set to ``False`` and is not secure against
58 malicious inputs. You should avoid setting ``safe=False``.
60 :param classes: If set to a single class, or a sequence (list, set, tuple) of
61 classes, then the classes will be made available when constructing objects.
62 If set to a dictionary of class names to class objects, the class object
63 will be provided to jsonpickle to deserialize the class name into.
64 This can be used to give jsonpickle access to local classes that are not
65 available through the global module import scope, and the dict method can
66 be used to deserialize encoded objects into a new class. An example of using
67 this argument can be found in examples/changing_class_path.py on GitHub.
69 :param on_missing: If set to 'error', it will raise an error if the class it's
70 decoding is not found. If set to 'warn', it will warn you in said case.
71 If set to a non-awaitable function, it will call said callback function
72 with the class name (a string) as the only parameter. Strings passed to
73 `on_missing` are lowercased automatically.
75 :param handle_readonly: If set to True, the Unpickler will handle objects encoded
76 with 'handle_readonly' properly. Do not set this flag for objects not encoded
77 with 'handle_readonly' set to True.
79 :param handler_context:
80 Pass custom context to a custom handler. This can be used to customize
81 behavior at runtime based off data. Defaults to ``None``. An example can
82 be found in the examples/ directory on GitHub.
84 >>> decode('"my string"') == 'my string'
85 True
86 >>> decode('36')
87 36
88 """
90 if isinstance(on_missing, str):
91 on_missing = on_missing.lower()
92 elif not util._is_function(on_missing):
93 warnings.warn(
94 "Unpickler.on_missing must be a string or a function! It will be ignored!"
95 )
97 is_ephemeral_context = context is None
98 context = context or Unpickler(
99 keys=keys,
100 safe=safe,
101 on_missing=on_missing,
102 handle_readonly=handle_readonly,
103 handler_context=handler_context,
104 )
105 if handler_context is not None:
106 context.handler_context = handler_context
107 data = json.decode(string)
108 result = context.restore(data, reset=reset, classes=classes)
109 if is_ephemeral_context:
110 # Avoid holding onto references to external objects, which can
111 # prevent garbage collection from occuring.
112 context.reset()
113 return result
116def _safe_hasattr(obj: Any, attr: str) -> bool:
117 """Workaround unreliable hasattr() availability on sqlalchemy objects"""
118 try:
119 object.__getattribute__(obj, attr)
120 return True
121 except AttributeError:
122 return False
125def _is_json_key(key: Any) -> bool:
126 """Has this key a special object that has been encoded to JSON?"""
127 return isinstance(key, str) and key.startswith(tags.JSON_KEY)
130class _Proxy:
131 """Proxies are dummy objects that are later replaced by real instances
133 The `restore()` function has to solve a tricky problem when pickling
134 objects with cyclical references -- the parent instance does not yet
135 exist.
137 The problem is that `__getnewargs__()`, `__getstate__()`, custom handlers,
138 and cyclical objects graphs are allowed to reference the yet-to-be-created
139 object via the referencing machinery.
141 In other words, objects are allowed to depend on themselves for
142 construction!
144 We solve this problem by placing dummy Proxy objects into the referencing
145 machinery so that we can construct the child objects before constructing
146 the parent. Objects are initially created with Proxy attribute values
147 instead of real references.
149 We collect all objects that contain references to proxies and run
150 a final sweep over them to swap in the real instance. This is done
151 at the very end of the top-level `restore()`.
153 The `instance` attribute below is replaced with the real instance
154 after `__new__()` has been used to construct the object and is used
155 when swapping proxies with real instances.
157 """
159 def __init__(self) -> None:
160 self.instance = None
162 def get(self) -> Any:
163 return self.instance
165 def reset(self, instance: Any) -> None:
166 self.instance = instance
169class _IDProxy(_Proxy):
170 def __init__(self, objs: list[Any], index: int) -> None:
171 self._index = index
172 self._objs = objs
174 def get(self) -> Any:
175 try:
176 return self._objs[self._index]
177 except IndexError:
178 return None
181def _obj_setattr(obj: Any, attr: str, proxy: _Proxy) -> None:
182 """Use setattr to update a proxy entry"""
183 setattr(obj, attr, proxy.get())
186def _obj_setvalue(obj: Any, idx: Any, proxy: _Proxy) -> None:
187 """Use obj[key] assignments to update a proxy entry"""
188 obj[idx] = proxy.get()
191def has_tag(obj: Any, tag: str) -> bool:
192 """Helper class that tests to see if the obj is a dictionary
193 and contains a particular key/tag.
195 >>> obj = {'test': 1}
196 >>> has_tag(obj, 'test')
197 True
198 >>> has_tag(obj, 'fail')
199 False
201 >>> has_tag(42, 'fail')
202 False
204 """
205 return type(obj) is dict and tag in obj
208def getargs(obj: dict[str, Any], classes: dict[str, type] | None = None) -> Any:
209 """Return arguments suitable for __new__()"""
210 # Let saved newargs take precedence over everything
211 if has_tag(obj, tags.NEWARGSEX):
212 raise ValueError("__newargs_ex__ returns both args and kwargs")
214 if has_tag(obj, tags.NEWARGS):
215 return obj[tags.NEWARGS]
217 if has_tag(obj, tags.INITARGS):
218 return obj[tags.INITARGS]
220 try:
221 seq_list = obj[tags.SEQ]
222 obj_dict = obj[tags.OBJECT]
223 except KeyError:
224 return []
225 typeref = util.loadclass(obj_dict, classes=classes)
226 if not typeref:
227 return []
228 if hasattr(typeref, "_fields") and len(typeref._fields) == len(seq_list):
229 return seq_list
230 return []
233class _trivialclassic:
234 """
235 A trivial class that can be instantiated with no args
236 """
239def make_blank_classic(cls: type) -> Any:
240 """
241 Implement the mandated strategy for dealing with classic classes
242 which cannot be instantiated without __getinitargs__ because they
243 take parameters
244 """
245 instance = _trivialclassic()
246 instance.__class__ = cls
247 return instance
250def loadrepr(reprstr: str) -> Any:
251 """Returns an instance of the object from the object's repr() string.
252 It involves the dynamic specification of code.
254 .. warning::
256 This function is unsafe and uses `eval()`.
258 >>> obj = loadrepr('datetime/datetime.datetime.now()')
259 >>> obj.__class__.__name__
260 'datetime'
262 """
263 module, evalstr = reprstr.split("/")
264 mylocals = locals()
265 localname = module
266 if "." in localname:
267 localname = module.split(".", 1)[0]
268 mylocals[localname] = __import__(module)
269 return eval(evalstr, mylocals)
272def _loadmodule(module_str: str) -> Any | None:
273 """Returns a reference to a module.
275 >>> fn = _loadmodule('datetime/datetime.datetime.fromtimestamp')
276 >>> fn.__name__
277 'fromtimestamp'
279 """
280 module, identifier = module_str.split("/")
281 try:
282 result = __import__(module)
283 except ImportError:
284 return None
285 identifier_parts = identifier.split(".")
286 first_identifier = identifier_parts[0]
287 if first_identifier != module and not module.startswith(f"{first_identifier}."):
288 return None
289 for name in identifier_parts[1:]:
290 try:
291 result = getattr(result, name)
292 except AttributeError:
293 return None
294 return result
297def has_tag_dict(obj: Any, tag: str) -> bool:
298 """Helper class that tests to see if the obj is a dictionary
299 and contains a particular key/tag.
301 >>> obj = {'test': 1}
302 >>> has_tag(obj, 'test')
303 True
304 >>> has_tag(obj, 'fail')
305 False
307 >>> has_tag(42, 'fail')
308 False
310 """
311 return tag in obj
314def _passthrough(value: Any) -> Any:
315 """A function that returns its input as-is"""
316 return value
319class Unpickler:
320 def __init__(
321 self,
322 keys: bool = True,
323 safe: bool = True,
324 on_missing: MissingHandler = "ignore",
325 handle_readonly: bool = False,
326 handler_context: Any = None,
327 ) -> None:
328 self.backend = json
329 self.keys = keys
330 self.safe = safe
331 self.on_missing = on_missing
332 self.handle_readonly = handle_readonly
333 # Custom context passed through to custom handlers, see #452
334 self.handler_context = handler_context
336 self.reset()
338 def reset(self) -> None:
339 """Resets the object's internal state."""
340 # Map of objects to their index in the _objs list
341 self._obj_to_idx = {}
342 self._objs = []
343 self._proxies = []
345 # Extra local classes not accessible globally
346 self._classes = {}
348 def _swap_proxies(self) -> None:
349 """Replace proxies with their corresponding instances"""
350 for obj, attr, proxy, method in self._proxies:
351 method(obj, attr, proxy)
352 self._proxies = []
354 def _restore(
355 self, obj: Any, _passthrough: Callable[[Any], Any] = _passthrough
356 ) -> Any:
357 # if obj isn't in these types, neither it nor nothing in it can have a tag
358 # don't change the tuple of types to a set, it won't work with isinstance
359 if not isinstance(obj, (str, list, dict, set, tuple)):
360 restore = _passthrough
361 else:
362 restore = self._restore_tags(obj)
363 return restore(obj)
365 def restore(
366 self, obj: Any, reset: bool = True, classes: ClassesType | None = None
367 ) -> Any:
368 """Restores a flattened object to its original python state.
370 Simply returns any of the basic builtin types
372 >>> u = Unpickler()
373 >>> u.restore('hello world') == 'hello world'
374 True
375 >>> u.restore({'key': 'value'}) == {'key': 'value'}
376 True
378 """
379 if reset:
380 self.reset()
381 if classes:
382 self.register_classes(classes)
383 value = self._restore(obj)
384 if reset:
385 self._swap_proxies()
386 return value
388 def register_classes(self, classes: ClassesType) -> None:
389 """Register one or more classes
391 :param classes: sequence of classes or a single class to register
393 """
394 if isinstance(classes, (list, tuple, set)):
395 for cls in classes:
396 self.register_classes(cls)
397 elif isinstance(classes, dict):
398 self._classes.update(
399 (
400 cls if isinstance(cls, str) else util.importable_name(cls),
401 handler,
402 )
403 for cls, handler in classes.items()
404 )
405 else:
406 self._classes[util.importable_name(classes)] = classes # type: ignore[arg-type]
408 def _restore_base64(self, obj: dict[str, Any]) -> bytes:
409 try:
410 return util.b64decode(obj[tags.B64].encode("utf-8"))
411 except (AttributeError, UnicodeEncodeError) as error:
412 warnings.warn(f"jsonpickle could not decode base64 payload: {error}")
413 return b""
415 def _restore_base85(self, obj: dict[str, Any]) -> bytes:
416 try:
417 return util.b85decode(obj[tags.B85].encode("utf-8"))
418 except (AttributeError, UnicodeEncodeError) as error:
419 warnings.warn(f"jsonpickle could not decode base85 payload: {error}")
420 return b""
422 def _restore_bytearray(self, obj: dict[str, Any]) -> bytearray:
423 payload = obj[tags.BYTEARRAY]
424 if tags.B85 in payload:
425 data = self._restore_base85(payload)
426 else:
427 data = self._restore_base64(payload)
428 return bytearray(data)
430 def _mkref(self, obj: Any) -> Any:
431 obj_id = id(obj)
432 try:
433 _ = self._obj_to_idx[obj_id]
434 except KeyError:
435 self._obj_to_idx[obj_id] = len(self._objs)
436 self._objs.append(obj)
437 return obj
439 def _restore_list(self, obj: list[Any]) -> list[Any]:
440 parent = []
441 self._mkref(parent)
442 children = [self._restore(v) for v in obj]
443 parent.extend(children)
444 method = _obj_setvalue
445 proxies = [
446 (parent, idx, value, method)
447 for idx, value in enumerate(parent)
448 if isinstance(value, _Proxy)
449 ]
450 self._proxies.extend(proxies)
451 return parent
453 def _restore_iterator(self, obj: dict[str, Any]) -> Iterator[Any]:
454 try:
455 return iter(self._restore_list(obj[tags.ITERATOR]))
456 except TypeError:
457 return iter([])
459 def _swapref(self, proxy: _Proxy, instance: Any) -> None:
460 proxy_id = id(proxy)
461 instance_id = id(instance)
463 instance_index = self._obj_to_idx[proxy_id]
464 self._obj_to_idx[instance_id] = instance_index
465 del self._obj_to_idx[proxy_id]
467 self._objs[instance_index] = instance
469 def _restore_reduce(self, obj: dict[str, Any]) -> Any:
470 """
471 Supports restoring with all elements of __reduce__ as per pep 307.
472 Assumes that iterator items (the last two) are represented as lists
473 as per pickler implementation.
474 """
475 proxy = _Proxy()
476 self._mkref(proxy)
477 try:
478 reduce_val = list(map(self._restore, obj[tags.REDUCE]))
479 except TypeError:
480 result = []
481 proxy.reset(result)
482 self._swapref(proxy, result)
483 return result
484 if len(reduce_val) < 6:
485 reduce_val.extend([None] * (6 - len(reduce_val)))
486 f, args, state, listitems, dictitems, state_setter = reduce_val
488 if f == tags.NEWOBJ or getattr(f, "__name__", "") == "__newobj__":
489 # mandated special case
490 cls = args[0]
491 if not isinstance(cls, type):
492 cls = self._restore(cls)
493 stage1 = cls.__new__(cls, *args[1:])
494 else:
495 if not callable(f):
496 result = []
497 proxy.reset(result)
498 self._swapref(proxy, result)
499 return result
500 try:
501 stage1 = f(*args)
502 except TypeError:
503 # this happens when there are missing kwargs and args don't match so we bypass
504 # __init__ since the state dict will set all attributes immediately afterwards
505 stage1 = f.__new__(f, *args)
507 if state and state_setter is None:
508 try:
509 stage1.__setstate__(state)
510 except AttributeError:
511 # it's fine - we'll try the prescribed default methods
512 try:
513 # we can't do a straight update here because we
514 # need object identity of the state dict to be
515 # preserved so that _swap_proxies works out
516 for k, v in stage1.__dict__.items():
517 state.setdefault(k, v)
518 stage1.__dict__ = state
519 except AttributeError:
520 # next prescribed default
521 try:
522 for k, v in state.items():
523 setattr(stage1, k, v)
524 except Exception: # ruff: ignore[BLE001]
525 dict_state, slots_state = state
526 if dict_state:
527 stage1.__dict__.update(dict_state)
528 if slots_state:
529 for k, v in slots_state.items():
530 setattr(stage1, k, v)
531 elif state:
532 # pickle protocol 5's state_setter takes priority over __setstate__
533 state_setter(stage1, state)
535 if listitems:
536 # should be lists if not None
537 try:
538 stage1.extend(listitems)
539 except AttributeError:
540 for x in listitems:
541 stage1.append(x)
543 if dictitems:
544 for k, v in dictitems:
545 stage1.__setitem__(k, v)
547 proxy.reset(stage1)
548 self._swapref(proxy, stage1)
549 return stage1
551 def _restore_id(self, obj: dict[str, Any]) -> Any:
552 try:
553 idx = obj[tags.ID]
554 return self._objs[idx]
555 except IndexError:
556 return _IDProxy(self._objs, idx)
557 except TypeError:
558 return None
560 def _restore_type(self, obj: dict[str, Any]) -> Any:
561 typeref = util.loadclass(obj[tags.TYPE], classes=self._classes)
562 if typeref is None:
563 return obj
564 return typeref
566 def _restore_module(self, obj: dict[str, Any]) -> Any:
567 new_obj = _loadmodule(obj[tags.MODULE])
568 return self._mkref(new_obj)
570 def _restore_repr_safe(self, obj: dict[str, Any]) -> Any:
571 new_obj = _loadmodule(obj[tags.REPR])
572 return self._mkref(new_obj)
574 def _restore_repr(self, obj: dict[str, Any]) -> Any:
575 obj = loadrepr(obj[tags.REPR])
576 return self._mkref(obj)
578 def _loadfactory(self, obj: dict[str, Any]) -> Any | None:
579 default_factory = None
580 for key in (tags.DEFAULT_FACTORY, "default_factory"):
581 try:
582 default_factory = obj.pop(key)
583 break
584 except KeyError:
585 continue
586 if default_factory is None:
587 return None
588 return self._restore(default_factory)
590 def _process_missing(self, class_name: str) -> None:
591 # most common case comes first
592 if self.on_missing == "ignore":
593 pass
594 elif self.on_missing == "warn":
595 warnings.warn(f"Unpickler._restore_object could not find {class_name}!")
596 elif self.on_missing == "error":
597 raise errors.ClassNotFoundError(
598 f"Unpickler.restore_object could not find {class_name}!"
599 )
600 elif util._is_function(self.on_missing):
601 self.on_missing(class_name) # type: ignore[operator]
603 def _restore_pickled_key(self, key: str) -> Any:
604 """Restore a possibly pickled key"""
605 if _is_json_key(key):
606 key = decode(
607 key[len(tags.JSON_KEY) :],
608 context=self,
609 keys=True,
610 reset=False,
611 )
612 return key
614 def _restore_key_fn(
615 self, _passthrough: Callable[[Any], Any] = _passthrough
616 ) -> Callable[[Any], Any]:
617 """Return a callable that restores keys
619 This function is responsible for restoring non-string keys
620 when we are decoding with `keys=True`.
622 """
623 # This function is called before entering a tight loop
624 # where the returned function will be called.
625 # We return a specific function after checking self.keys
626 # instead of doing so in the body of the function to
627 # avoid conditional branching inside a tight loop.
628 if self.keys:
629 restore_key = self._restore_pickled_key
630 else:
631 restore_key = _passthrough # type: ignore[assignment]
632 return restore_key
634 def _restore_from_dict(
635 self,
636 obj: dict[str, Any],
637 instance: Any,
638 ignorereserved: bool = True,
639 restore_dict_items: bool = True,
640 ) -> Any:
641 restore_key = self._restore_key_fn()
642 method = _obj_setattr
643 deferred = {}
645 for k, v in util.items(obj):
646 # ignore the reserved attribute
647 if ignorereserved and k in tags.RESERVED:
648 continue
649 if restore_dict_items:
650 k = restore_key(k)
651 value = self._restore(v)
652 else:
653 value = v
654 if util._is_noncomplex(instance) or util._is_dictionary_subclass(instance):
655 try:
656 if k == "__dict__":
657 setattr(instance, k, value)
658 else:
659 instance[k] = value
660 except TypeError:
661 # Immutable object, must be constructed in one shot
662 if k != "__dict__":
663 deferred[k] = value
664 continue
665 else:
666 if not k.startswith("__"):
667 try:
668 setattr(instance, k, value)
669 except KeyError:
670 # certain numpy objects require us to prepend a _ to the var
671 # this should go in the np handler but I think this could be
672 # useful for other code
673 setattr(instance, f"_{k}", value)
674 except dataclasses.FrozenInstanceError:
675 # issue #240
676 # i think this is the only way to set frozen dataclass attrs
677 object.__setattr__(instance, k, value)
678 except AttributeError:
679 # some objects raise this for read-only attributes (#422) (#478)
680 if (
681 hasattr(instance, "__slots__")
682 and not len(instance.__slots__)
683 # we have to handle this separately because of +483
684 and issubclass(instance.__class__, (int, str))
685 and self.handle_readonly
686 ):
687 continue
688 raise
689 else:
690 setattr(instance, f"_{instance.__class__.__name__}{k}", value)
692 # This instance has an instance variable named `k` that is
693 # currently a proxy and must be replaced
694 if isinstance(value, _Proxy):
695 self._proxies.append((instance, k, value, method))
697 if deferred:
698 # SQLAlchemy Immutable mappings must be constructed in one shot
699 instance = instance.__class__(deferred)
701 return instance
703 def _restore_state(self, obj: dict[str, Any], instance: Any) -> Any:
704 state = self._restore(obj[tags.STATE])
705 has_slots = (
706 isinstance(state, tuple) and len(state) == 2 and isinstance(state[1], dict)
707 )
708 has_slots_and_dict = has_slots and isinstance(state[0], dict)
709 if hasattr(instance, "__setstate__"):
710 instance.__setstate__(state)
711 elif isinstance(state, dict):
712 # implements described default handling
713 # of state for object with instance dict
714 # and no slots
715 instance = self._restore_from_dict(
716 state, instance, ignorereserved=False, restore_dict_items=False
717 )
718 elif has_slots:
719 instance = self._restore_from_dict(
720 state[1], instance, ignorereserved=False, restore_dict_items=False
721 )
722 if has_slots_and_dict:
723 instance = self._restore_from_dict(
724 state[0], instance, ignorereserved=False, restore_dict_items=False
725 )
726 elif not hasattr(instance, "__getnewargs__") and not hasattr(
727 instance, "__getnewargs_ex__"
728 ):
729 # __setstate__ is not implemented so that means that the best
730 # we can do is return the result of __getstate__() rather than
731 # return an empty shell of an object.
732 # However, if there were newargs, it's not an empty shell
733 instance = state
734 return instance
736 def _restore_object_instance_variables(
737 self, obj: dict[str, Any], instance: Any
738 ) -> Any:
739 instance = self._restore_from_dict(obj, instance)
741 # Handle list and set subclasses
742 if has_tag(obj, tags.SEQ):
743 if hasattr(instance, "append"):
744 for v in obj[tags.SEQ]:
745 instance.append(self._restore(v))
746 elif hasattr(instance, "add"):
747 for v in obj[tags.SEQ]:
748 instance.add(self._restore(v))
750 if has_tag(obj, tags.STATE):
751 instance = self._restore_state(obj, instance)
753 return instance
755 def _restore_object_instance(
756 self, obj: dict[str, Any], cls: type, class_name: str = ""
757 ) -> Any:
758 # This is a placeholder proxy object which allows child objects to
759 # reference the parent object before it has been instantiated.
760 proxy = _Proxy()
761 self._mkref(proxy)
763 # An object can install itself as its own factory, so load the factory
764 # after the instance is available for referencing.
765 factory = self._loadfactory(obj)
767 if has_tag(obj, tags.NEWARGSEX):
768 args, kwargs = obj[tags.NEWARGSEX]
769 else:
770 args = getargs(obj, classes=self._classes)
771 kwargs = {}
772 if args:
773 args = self._restore(args)
774 if kwargs:
775 kwargs = self._restore(kwargs)
777 is_oldstyle = not (isinstance(cls, type) or getattr(cls, "__meta__", None))
778 try:
779 if not is_oldstyle and hasattr(cls, "__new__"):
780 # new style classes
781 if factory:
782 instance = cls.__new__(cls, factory, *args, **kwargs)
783 instance.default_factory = factory
784 else:
785 instance = cls.__new__(cls, *args, **kwargs)
786 else:
787 instance = object.__new__(cls)
788 except TypeError: # old-style classes
789 is_oldstyle = True
791 if is_oldstyle:
792 try:
793 instance = cls(*args)
794 except TypeError: # fail gracefully
795 try:
796 instance = make_blank_classic(cls)
797 except Exception: # ruff: ignore[BLE001]
798 self._process_missing(class_name)
799 return self._mkref(obj)
801 proxy.reset(instance)
802 self._swapref(proxy, instance)
804 if isinstance(instance, tuple):
805 return instance
807 instance = self._restore_object_instance_variables(obj, instance)
809 if _safe_hasattr(instance, "default_factory") and isinstance(
810 instance.default_factory, _Proxy
811 ):
812 instance.default_factory = instance.default_factory.get()
814 return instance
816 def _restore_object(self, obj: dict[str, Any]) -> Any:
817 class_name = obj[tags.OBJECT]
818 cls = util.loadclass(class_name, classes=self._classes)
819 handler = handlers.get(cls, handlers.get(class_name)) # type: ignore[arg-type]
820 if handler is not None: # custom handler
821 proxy = _Proxy()
822 self._mkref(proxy)
823 handler_instance = handler(self)
824 instance = self._call_handler_restore(handler_instance, obj)
825 proxy.reset(instance)
826 self._swapref(proxy, instance)
827 return instance
829 if cls is None:
830 self._process_missing(class_name)
831 return self._mkref(obj)
833 return self._restore_object_instance(obj, cls, class_name)
835 def _restore_function(self, obj: dict[str, Any]) -> Any:
836 return util.loadclass(obj[tags.FUNCTION], classes=self._classes)
838 def _restore_set(self, obj: dict[str, Any]) -> set[Any]:
839 try:
840 return {self._restore(v) for v in obj[tags.SET]}
841 except TypeError:
842 return set()
844 def _restore_dict(self, obj: dict[str, Any]) -> dict[str, Any]:
845 data = {}
846 self._mkref(data)
848 # If we are decoding dicts that can have non-string keys then we
849 # need to do a two-phase decode where the non-string keys are
850 # processed last. This ensures a deterministic order when
851 # assigning object IDs for references.
852 if self.keys:
853 # Phase 1: regular non-special keys.
854 for k, v in util.items(obj):
855 if _is_json_key(k):
856 continue
857 data[k] = result = self._restore(v)
858 if isinstance(result, _Proxy):
859 self._proxies.append((data, k, result, _obj_setvalue))
861 # Phase 2: object keys only.
862 for k, v in util.items(obj):
863 if not _is_json_key(k):
864 continue
865 restored_key = self._restore_pickled_key(k)
866 result = self._restore(v)
867 try:
868 data[restored_key] = result
869 except TypeError: # fail gracefully
870 # The encoder can never emit an unhashable key, so if this
871 # is triggered then we're dealing with hand-crafted input.
872 # Keep the raw json:// key rather than failing the whole decode
873 data[k] = result
874 else:
875 k = restored_key
876 # k is currently a proxy and must be replaced
877 if isinstance(result, _Proxy):
878 self._proxies.append((data, k, result, _obj_setvalue))
879 else:
880 # No special keys, thus we don't need to restore the keys either.
881 for k, v in util.items(obj):
882 data[k] = result = self._restore(v)
883 if isinstance(result, _Proxy):
884 self._proxies.append((data, k, result, _obj_setvalue))
885 return data
887 def _restore_tuple(self, obj: dict[str, Any]) -> tuple[Any, ...]:
888 try:
889 return tuple(self._restore(v) for v in obj[tags.TUPLE])
890 except TypeError:
891 return ()
893 def _restore_tags(
894 self, obj: Any, _passthrough: Callable[[Any], Any] = _passthrough
895 ) -> Callable[[Any], Any]:
896 """Return the restoration function for the specified object"""
897 try:
898 if not tags.RESERVED <= set(obj) and type(obj) not in (list, dict):
899 return _passthrough
900 except TypeError:
901 pass
902 if type(obj) is dict:
903 if tags.TUPLE in obj:
904 restore = self._restore_tuple
905 elif tags.SET in obj:
906 restore = self._restore_set # type: ignore[assignment]
907 elif tags.B64 in obj:
908 restore = self._restore_base64 # type: ignore[assignment]
909 elif tags.B85 in obj:
910 restore = self._restore_base85 # type: ignore[assignment]
911 elif tags.BYTEARRAY in obj:
912 restore = self._restore_bytearray # type: ignore[assignment]
913 elif tags.ID in obj:
914 restore = self._restore_id
915 elif tags.ITERATOR in obj:
916 restore = self._restore_iterator # type: ignore[assignment]
917 elif tags.OBJECT in obj:
918 restore = self._restore_object
919 elif tags.TYPE in obj:
920 restore = self._restore_type
921 elif tags.REDUCE in obj:
922 restore = self._restore_reduce
923 elif tags.FUNCTION in obj:
924 restore = self._restore_function
925 elif tags.MODULE in obj:
926 restore = self._restore_module
927 elif tags.REPR in obj:
928 if self.safe:
929 restore = self._restore_repr_safe
930 else:
931 restore = self._restore_repr
932 else:
933 restore = self._restore_dict # type: ignore[assignment]
934 elif type(obj) is list:
935 restore = self._restore_list # type: ignore[assignment]
936 else:
937 restore = _passthrough # type: ignore[assignment]
938 return restore
940 def _call_handler_restore(
941 self, handler: handlers.BaseHandler, obj: dict[str, Any]
942 ) -> Any:
943 kwargs: dict[str, Any] = {}
944 if (
945 self.handler_context is not None
946 and handlers.handler_accepts_handler_context(handler.restore)
947 ):
948 kwargs["handler_context"] = self.handler_context
949 return handler.restore(obj, **kwargs)