Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jsonpickle/unpickler.py: 70%
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 _restore_memoryview(self, obj: dict[str, Any]) -> memoryview:
431 payload = obj[tags.MEMORYVIEW]
432 if tags.B85 in payload:
433 data = self._restore_base85(payload)
434 else:
435 data = self._restore_base64(payload)
436 return memoryview(data)
438 def _mkref(self, obj: Any) -> Any:
439 obj_id = id(obj)
440 try:
441 _ = self._obj_to_idx[obj_id]
442 except KeyError:
443 self._obj_to_idx[obj_id] = len(self._objs)
444 self._objs.append(obj)
445 return obj
447 def _restore_list(self, obj: list[Any]) -> list[Any]:
448 parent = []
449 self._mkref(parent)
450 children = [self._restore(v) for v in obj]
451 parent.extend(children)
452 method = _obj_setvalue
453 proxies = [
454 (parent, idx, value, method)
455 for idx, value in enumerate(parent)
456 if isinstance(value, _Proxy)
457 ]
458 self._proxies.extend(proxies)
459 return parent
461 def _restore_iterator(self, obj: dict[str, Any]) -> Iterator[Any]:
462 try:
463 return iter(self._restore_list(obj[tags.ITERATOR]))
464 except TypeError:
465 return iter([])
467 def _swapref(self, proxy: _Proxy, instance: Any) -> None:
468 proxy_id = id(proxy)
469 instance_id = id(instance)
471 instance_index = self._obj_to_idx[proxy_id]
472 self._obj_to_idx[instance_id] = instance_index
473 del self._obj_to_idx[proxy_id]
475 self._objs[instance_index] = instance
477 def _restore_reduce(self, obj: dict[str, Any]) -> Any:
478 """
479 Supports restoring with all elements of __reduce__ as per pep 307.
480 Assumes that iterator items (the last two) are represented as lists
481 as per pickler implementation.
482 """
483 proxy = _Proxy()
484 self._mkref(proxy)
485 try:
486 reduce_val = list(map(self._restore, obj[tags.REDUCE]))
487 except TypeError:
488 result = []
489 proxy.reset(result)
490 self._swapref(proxy, result)
491 return result
492 if len(reduce_val) < 6:
493 reduce_val.extend([None] * (6 - len(reduce_val)))
494 f, args, state, listitems, dictitems, state_setter = reduce_val
496 if f == tags.NEWOBJ or getattr(f, "__name__", "") == "__newobj__":
497 # mandated special case
498 cls = args[0]
499 if not isinstance(cls, type):
500 cls = self._restore(cls)
501 stage1 = cls.__new__(cls, *args[1:])
502 else:
503 if not callable(f):
504 result = []
505 proxy.reset(result)
506 self._swapref(proxy, result)
507 return result
508 try:
509 stage1 = f(*args)
510 except TypeError:
511 # this happens when there are missing kwargs and args don't match so we bypass
512 # __init__ since the state dict will set all attributes immediately afterwards
513 stage1 = f.__new__(f, *args)
515 if state and state_setter is None:
516 try:
517 stage1.__setstate__(state)
518 except AttributeError:
519 # it's fine - we'll try the prescribed default methods
520 try:
521 # we can't do a straight update here because we
522 # need object identity of the state dict to be
523 # preserved so that _swap_proxies works out
524 for k, v in stage1.__dict__.items():
525 state.setdefault(k, v)
526 stage1.__dict__ = state
527 except AttributeError:
528 # next prescribed default
529 try:
530 for k, v in state.items():
531 setattr(stage1, k, v)
532 except Exception: # ruff: ignore[BLE001]
533 dict_state, slots_state = state
534 if dict_state:
535 stage1.__dict__.update(dict_state)
536 if slots_state:
537 for k, v in slots_state.items():
538 setattr(stage1, k, v)
539 elif state:
540 # pickle protocol 5's state_setter takes priority over __setstate__
541 state_setter(stage1, state)
543 if listitems:
544 # should be lists if not None
545 try:
546 stage1.extend(listitems)
547 except AttributeError:
548 for x in listitems:
549 stage1.append(x)
551 if dictitems:
552 for k, v in dictitems:
553 stage1.__setitem__(k, v)
555 proxy.reset(stage1)
556 self._swapref(proxy, stage1)
557 return stage1
559 def _restore_id(self, obj: dict[str, Any]) -> Any:
560 try:
561 idx = obj[tags.ID]
562 return self._objs[idx]
563 except IndexError:
564 return _IDProxy(self._objs, idx)
565 except TypeError:
566 return None
568 def _restore_type(self, obj: dict[str, Any]) -> Any:
569 typeref = util.loadclass(obj[tags.TYPE], classes=self._classes)
570 if typeref is None:
571 return obj
572 return typeref
574 def _restore_module(self, obj: dict[str, Any]) -> Any:
575 new_obj = _loadmodule(obj[tags.MODULE])
576 return self._mkref(new_obj)
578 def _restore_repr_safe(self, obj: dict[str, Any]) -> Any:
579 new_obj = _loadmodule(obj[tags.REPR])
580 return self._mkref(new_obj)
582 def _restore_repr(self, obj: dict[str, Any]) -> Any:
583 obj = loadrepr(obj[tags.REPR])
584 return self._mkref(obj)
586 def _loadfactory(self, obj: dict[str, Any]) -> Any | None:
587 default_factory = None
588 for key in (tags.DEFAULT_FACTORY, "default_factory"):
589 try:
590 default_factory = obj.pop(key)
591 break
592 except KeyError:
593 continue
594 if default_factory is None:
595 return None
596 return self._restore(default_factory)
598 def _process_missing(self, class_name: str) -> None:
599 # most common case comes first
600 if self.on_missing == "ignore":
601 pass
602 elif self.on_missing == "warn":
603 warnings.warn(f"Unpickler._restore_object could not find {class_name}!")
604 elif self.on_missing == "error":
605 raise errors.ClassNotFoundError(
606 f"Unpickler.restore_object could not find {class_name}!"
607 )
608 elif util._is_function(self.on_missing):
609 self.on_missing(class_name) # type: ignore[operator]
611 def _restore_pickled_key(self, key: str) -> Any:
612 """Restore a possibly pickled key"""
613 if _is_json_key(key):
614 key = decode(
615 key[len(tags.JSON_KEY) :],
616 context=self,
617 keys=True,
618 reset=False,
619 )
620 return key
622 def _restore_key_fn(
623 self, _passthrough: Callable[[Any], Any] = _passthrough
624 ) -> Callable[[Any], Any]:
625 """Return a callable that restores keys
627 This function is responsible for restoring non-string keys
628 when we are decoding with `keys=True`.
630 """
631 # This function is called before entering a tight loop
632 # where the returned function will be called.
633 # We return a specific function after checking self.keys
634 # instead of doing so in the body of the function to
635 # avoid conditional branching inside a tight loop.
636 if self.keys:
637 restore_key = self._restore_pickled_key
638 else:
639 restore_key = _passthrough # type: ignore[assignment]
640 return restore_key
642 def _restore_from_dict(
643 self,
644 obj: dict[str, Any],
645 instance: Any,
646 ignorereserved: bool = True,
647 restore_dict_items: bool = True,
648 ) -> Any:
649 restore_key = self._restore_key_fn()
650 method = _obj_setattr
651 deferred = {}
653 for k, v in util.items(obj):
654 # ignore the reserved attribute
655 if ignorereserved and k in tags.RESERVED:
656 continue
657 if restore_dict_items:
658 k = restore_key(k)
659 value = self._restore(v)
660 else:
661 value = v
662 if util._is_noncomplex(instance) or util._is_dictionary_subclass(instance):
663 try:
664 if k == "__dict__":
665 setattr(instance, k, value)
666 else:
667 instance[k] = value
668 except TypeError:
669 # Immutable object, must be constructed in one shot
670 if k != "__dict__":
671 deferred[k] = value
672 continue
673 else:
674 if not k.startswith("__"):
675 try:
676 setattr(instance, k, value)
677 except KeyError:
678 # certain numpy objects require us to prepend a _ to the var
679 # this should go in the np handler but I think this could be
680 # useful for other code
681 setattr(instance, f"_{k}", value)
682 except dataclasses.FrozenInstanceError:
683 # issue #240
684 # i think this is the only way to set frozen dataclass attrs
685 object.__setattr__(instance, k, value)
686 except AttributeError:
687 # some objects raise this for read-only attributes (#422) (#478)
688 if (
689 hasattr(instance, "__slots__")
690 and not len(instance.__slots__)
691 # we have to handle this separately because of +483
692 and issubclass(instance.__class__, (int, str))
693 and self.handle_readonly
694 ):
695 continue
696 raise
697 else:
698 setattr(instance, f"_{instance.__class__.__name__}{k}", value)
700 # This instance has an instance variable named `k` that is
701 # currently a proxy and must be replaced
702 if isinstance(value, _Proxy):
703 self._proxies.append((instance, k, value, method))
705 if deferred:
706 # SQLAlchemy Immutable mappings must be constructed in one shot
707 instance = instance.__class__(deferred)
709 return instance
711 def _restore_state(self, obj: dict[str, Any], instance: Any) -> Any:
712 state = self._restore(obj[tags.STATE])
713 has_slots = (
714 isinstance(state, tuple) and len(state) == 2 and isinstance(state[1], dict)
715 )
716 has_slots_and_dict = has_slots and isinstance(state[0], dict)
717 if hasattr(instance, "__setstate__"):
718 instance.__setstate__(state)
719 elif isinstance(state, dict):
720 # implements described default handling
721 # of state for object with instance dict
722 # and no slots
723 instance = self._restore_from_dict(
724 state, instance, ignorereserved=False, restore_dict_items=False
725 )
726 elif has_slots:
727 instance = self._restore_from_dict(
728 state[1], instance, ignorereserved=False, restore_dict_items=False
729 )
730 if has_slots_and_dict:
731 instance = self._restore_from_dict(
732 state[0], instance, ignorereserved=False, restore_dict_items=False
733 )
734 elif not hasattr(instance, "__getnewargs__") and not hasattr(
735 instance, "__getnewargs_ex__"
736 ):
737 # __setstate__ is not implemented so that means that the best
738 # we can do is return the result of __getstate__() rather than
739 # return an empty shell of an object.
740 # However, if there were newargs, it's not an empty shell
741 instance = state
742 return instance
744 def _restore_object_instance_variables(
745 self, obj: dict[str, Any], instance: Any
746 ) -> Any:
747 instance = self._restore_from_dict(obj, instance)
749 # Handle list and set subclasses
750 if has_tag(obj, tags.SEQ):
751 if hasattr(instance, "append"):
752 for v in obj[tags.SEQ]:
753 instance.append(self._restore(v))
754 elif hasattr(instance, "add"):
755 for v in obj[tags.SEQ]:
756 instance.add(self._restore(v))
758 if has_tag(obj, tags.STATE):
759 instance = self._restore_state(obj, instance)
761 return instance
763 def _restore_object_instance(
764 self, obj: dict[str, Any], cls: type, class_name: str = ""
765 ) -> Any:
766 # This is a placeholder proxy object which allows child objects to
767 # reference the parent object before it has been instantiated.
768 proxy = _Proxy()
769 self._mkref(proxy)
771 # An object can install itself as its own factory, so load the factory
772 # after the instance is available for referencing.
773 factory = self._loadfactory(obj)
775 if has_tag(obj, tags.NEWARGSEX):
776 args, kwargs = obj[tags.NEWARGSEX]
777 else:
778 args = getargs(obj, classes=self._classes)
779 kwargs = {}
780 if args:
781 args = self._restore(args)
782 if kwargs:
783 kwargs = self._restore(kwargs)
785 is_oldstyle = not (isinstance(cls, type) or getattr(cls, "__meta__", None))
786 try:
787 if not is_oldstyle and hasattr(cls, "__new__"):
788 # new style classes
789 if factory:
790 instance = cls.__new__(cls, factory, *args, **kwargs)
791 instance.default_factory = factory
792 else:
793 instance = cls.__new__(cls, *args, **kwargs)
794 else:
795 instance = object.__new__(cls)
796 except TypeError: # old-style classes
797 is_oldstyle = True
799 if is_oldstyle:
800 try:
801 instance = cls(*args)
802 except TypeError: # fail gracefully
803 try:
804 instance = make_blank_classic(cls)
805 except Exception: # ruff: ignore[BLE001]
806 self._process_missing(class_name)
807 return self._mkref(obj)
809 proxy.reset(instance)
810 self._swapref(proxy, instance)
812 if isinstance(instance, tuple):
813 return instance
815 instance = self._restore_object_instance_variables(obj, instance)
817 if _safe_hasattr(instance, "default_factory") and isinstance(
818 instance.default_factory, _Proxy
819 ):
820 instance.default_factory = instance.default_factory.get()
822 return instance
824 def _restore_object(self, obj: dict[str, Any]) -> Any:
825 class_name = obj[tags.OBJECT]
826 cls = util.loadclass(class_name, classes=self._classes)
827 handler = handlers.get(cls, handlers.get(class_name)) # type: ignore[arg-type]
828 if handler is not None: # custom handler
829 proxy = _Proxy()
830 self._mkref(proxy)
831 handler_instance = handler(self)
832 instance = self._call_handler_restore(handler_instance, obj)
833 proxy.reset(instance)
834 self._swapref(proxy, instance)
835 return instance
837 if cls is None:
838 self._process_missing(class_name)
839 return self._mkref(obj)
841 return self._restore_object_instance(obj, cls, class_name)
843 def _restore_function(self, obj: dict[str, Any]) -> Any:
844 return util.loadclass(obj[tags.FUNCTION], classes=self._classes)
846 def _restore_set(self, obj: dict[str, Any]) -> set[Any]:
847 try:
848 return {self._restore(v) for v in obj[tags.SET]}
849 except TypeError:
850 return set()
852 def _restore_dict(self, obj: dict[str, Any]) -> dict[str, Any]:
853 data = {}
854 self._mkref(data)
856 # If we are decoding dicts that can have non-string keys then we
857 # need to do a two-phase decode where the non-string keys are
858 # processed last. This ensures a deterministic order when
859 # assigning object IDs for references.
860 if self.keys:
861 # Phase 1: regular non-special keys.
862 for k, v in util.items(obj):
863 if _is_json_key(k):
864 continue
865 data[k] = result = self._restore(v)
866 if isinstance(result, _Proxy):
867 self._proxies.append((data, k, result, _obj_setvalue))
869 # Phase 2: object keys only.
870 for k, v in util.items(obj):
871 if not _is_json_key(k):
872 continue
873 restored_key = self._restore_pickled_key(k)
874 result = self._restore(v)
875 try:
876 data[restored_key] = result
877 except TypeError: # fail gracefully
878 # The encoder can never emit an unhashable key, so if this
879 # is triggered then we're dealing with hand-crafted input.
880 # Keep the raw json:// key rather than failing the whole decode
881 data[k] = result
882 else:
883 k = restored_key
884 # k is currently a proxy and must be replaced
885 if isinstance(result, _Proxy):
886 self._proxies.append((data, k, result, _obj_setvalue))
887 else:
888 # No special keys, thus we don't need to restore the keys either.
889 for k, v in util.items(obj):
890 data[k] = result = self._restore(v)
891 if isinstance(result, _Proxy):
892 self._proxies.append((data, k, result, _obj_setvalue))
893 return data
895 def _restore_tuple(self, obj: dict[str, Any]) -> tuple[Any, ...]:
896 try:
897 return tuple(self._restore(v) for v in obj[tags.TUPLE])
898 except TypeError:
899 return ()
901 def _restore_tags(
902 self, obj: Any, _passthrough: Callable[[Any], Any] = _passthrough
903 ) -> Callable[[Any], Any]:
904 """Return the restoration function for the specified object"""
905 try:
906 if not tags.RESERVED <= set(obj) and type(obj) not in (list, dict):
907 return _passthrough
908 except TypeError:
909 pass
910 if type(obj) is dict:
911 if tags.TUPLE in obj:
912 restore = self._restore_tuple
913 elif tags.SET in obj:
914 restore = self._restore_set # type: ignore[assignment]
915 elif tags.B64 in obj:
916 restore = self._restore_base64 # type: ignore[assignment]
917 elif tags.B85 in obj:
918 restore = self._restore_base85 # type: ignore[assignment]
919 elif tags.BYTEARRAY in obj:
920 restore = self._restore_bytearray # type: ignore[assignment]
921 elif tags.MEMORYVIEW in obj:
922 restore = self._restore_memoryview # type: ignore[assignment]
923 elif tags.ID in obj:
924 restore = self._restore_id
925 elif tags.ITERATOR in obj:
926 restore = self._restore_iterator # type: ignore[assignment]
927 elif tags.OBJECT in obj:
928 restore = self._restore_object
929 elif tags.TYPE in obj:
930 restore = self._restore_type
931 elif tags.REDUCE in obj:
932 restore = self._restore_reduce
933 elif tags.FUNCTION in obj:
934 restore = self._restore_function
935 elif tags.MODULE in obj:
936 restore = self._restore_module
937 elif tags.REPR in obj:
938 if self.safe:
939 restore = self._restore_repr_safe
940 else:
941 restore = self._restore_repr
942 else:
943 restore = self._restore_dict # type: ignore[assignment]
944 elif type(obj) is list:
945 restore = self._restore_list # type: ignore[assignment]
946 else:
947 restore = _passthrough # type: ignore[assignment]
948 return restore
950 def _call_handler_restore(
951 self, handler: handlers.BaseHandler, obj: dict[str, Any]
952 ) -> Any:
953 kwargs: dict[str, Any] = {}
954 if (
955 self.handler_context is not None
956 and handlers.handler_accepts_handler_context(handler.restore)
957 ):
958 kwargs["handler_context"] = self.handler_context
959 return handler.restore(obj, **kwargs)