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

512 statements  

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 

11 

12from . import errors, handlers, tags, util 

13from .backend import json 

14 

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] 

19 

20 

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. 

34 

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. 

40 

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. 

44 

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. 

50 

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. 

54 

55 .. warning:: 

56 

57 ``eval()`` is used when set to ``False`` and is not secure against 

58 malicious inputs. You should avoid setting ``safe=False``. 

59 

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. 

68 

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. 

74 

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. 

78 

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. 

83 

84 >>> decode('"my string"') == 'my string' 

85 True 

86 >>> decode('36') 

87 36 

88 """ 

89 

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 ) 

96 

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 

114 

115 

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 

123 

124 

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) 

128 

129 

130class _Proxy: 

131 """Proxies are dummy objects that are later replaced by real instances 

132 

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. 

136 

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. 

140 

141 In other words, objects are allowed to depend on themselves for 

142 construction! 

143 

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. 

148 

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()`. 

152 

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. 

156 

157 """ 

158 

159 def __init__(self) -> None: 

160 self.instance = None 

161 

162 def get(self) -> Any: 

163 return self.instance 

164 

165 def reset(self, instance: Any) -> None: 

166 self.instance = instance 

167 

168 

169class _IDProxy(_Proxy): 

170 def __init__(self, objs: list[Any], index: int) -> None: 

171 self._index = index 

172 self._objs = objs 

173 

174 def get(self) -> Any: 

175 try: 

176 return self._objs[self._index] 

177 except IndexError: 

178 return None 

179 

180 

181def _obj_setattr(obj: Any, attr: str, proxy: _Proxy) -> None: 

182 """Use setattr to update a proxy entry""" 

183 setattr(obj, attr, proxy.get()) 

184 

185 

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() 

189 

190 

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. 

194 

195 >>> obj = {'test': 1} 

196 >>> has_tag(obj, 'test') 

197 True 

198 >>> has_tag(obj, 'fail') 

199 False 

200 

201 >>> has_tag(42, 'fail') 

202 False 

203 

204 """ 

205 return type(obj) is dict and tag in obj 

206 

207 

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") 

213 

214 if has_tag(obj, tags.NEWARGS): 

215 return obj[tags.NEWARGS] 

216 

217 if has_tag(obj, tags.INITARGS): 

218 return obj[tags.INITARGS] 

219 

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 [] 

231 

232 

233class _trivialclassic: 

234 """ 

235 A trivial class that can be instantiated with no args 

236 """ 

237 

238 

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 

248 

249 

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. 

253 

254 .. warning:: 

255 

256 This function is unsafe and uses `eval()`. 

257 

258 >>> obj = loadrepr('datetime/datetime.datetime.now()') 

259 >>> obj.__class__.__name__ 

260 'datetime' 

261 

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) 

270 

271 

272def _loadmodule(module_str: str) -> Any | None: 

273 """Returns a reference to a module. 

274 

275 >>> fn = _loadmodule('datetime/datetime.datetime.fromtimestamp') 

276 >>> fn.__name__ 

277 'fromtimestamp' 

278 

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 

295 

296 

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. 

300 

301 >>> obj = {'test': 1} 

302 >>> has_tag(obj, 'test') 

303 True 

304 >>> has_tag(obj, 'fail') 

305 False 

306 

307 >>> has_tag(42, 'fail') 

308 False 

309 

310 """ 

311 return tag in obj 

312 

313 

314def _passthrough(value: Any) -> Any: 

315 """A function that returns its input as-is""" 

316 return value 

317 

318 

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 

335 

336 self.reset() 

337 

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 = [] 

344 

345 # Extra local classes not accessible globally 

346 self._classes = {} 

347 

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 = [] 

353 

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) 

364 

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. 

369 

370 Simply returns any of the basic builtin types 

371 

372 >>> u = Unpickler() 

373 >>> u.restore('hello world') == 'hello world' 

374 True 

375 >>> u.restore({'key': 'value'}) == {'key': 'value'} 

376 True 

377 

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 

387 

388 def register_classes(self, classes: ClassesType) -> None: 

389 """Register one or more classes 

390 

391 :param classes: sequence of classes or a single class to register 

392 

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] 

407 

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"" 

414 

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"" 

421 

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) 

429 

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) 

437 

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 

446 

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 

460 

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([]) 

466 

467 def _swapref(self, proxy: _Proxy, instance: Any) -> None: 

468 proxy_id = id(proxy) 

469 instance_id = id(instance) 

470 

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] 

474 

475 self._objs[instance_index] = instance 

476 

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 

495 

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) 

514 

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) 

542 

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) 

550 

551 if dictitems: 

552 for k, v in dictitems: 

553 stage1.__setitem__(k, v) 

554 

555 proxy.reset(stage1) 

556 self._swapref(proxy, stage1) 

557 return stage1 

558 

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 

567 

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 

573 

574 def _restore_module(self, obj: dict[str, Any]) -> Any: 

575 new_obj = _loadmodule(obj[tags.MODULE]) 

576 return self._mkref(new_obj) 

577 

578 def _restore_repr_safe(self, obj: dict[str, Any]) -> Any: 

579 new_obj = _loadmodule(obj[tags.REPR]) 

580 return self._mkref(new_obj) 

581 

582 def _restore_repr(self, obj: dict[str, Any]) -> Any: 

583 obj = loadrepr(obj[tags.REPR]) 

584 return self._mkref(obj) 

585 

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) 

597 

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] 

610 

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 

621 

622 def _restore_key_fn( 

623 self, _passthrough: Callable[[Any], Any] = _passthrough 

624 ) -> Callable[[Any], Any]: 

625 """Return a callable that restores keys 

626 

627 This function is responsible for restoring non-string keys 

628 when we are decoding with `keys=True`. 

629 

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 

641 

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 = {} 

652 

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) 

699 

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)) 

704 

705 if deferred: 

706 # SQLAlchemy Immutable mappings must be constructed in one shot 

707 instance = instance.__class__(deferred) 

708 

709 return instance 

710 

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 

743 

744 def _restore_object_instance_variables( 

745 self, obj: dict[str, Any], instance: Any 

746 ) -> Any: 

747 instance = self._restore_from_dict(obj, instance) 

748 

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)) 

757 

758 if has_tag(obj, tags.STATE): 

759 instance = self._restore_state(obj, instance) 

760 

761 return instance 

762 

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) 

770 

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) 

774 

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) 

784 

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 

798 

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) 

808 

809 proxy.reset(instance) 

810 self._swapref(proxy, instance) 

811 

812 if isinstance(instance, tuple): 

813 return instance 

814 

815 instance = self._restore_object_instance_variables(obj, instance) 

816 

817 if _safe_hasattr(instance, "default_factory") and isinstance( 

818 instance.default_factory, _Proxy 

819 ): 

820 instance.default_factory = instance.default_factory.get() 

821 

822 return instance 

823 

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 

836 

837 if cls is None: 

838 self._process_missing(class_name) 

839 return self._mkref(obj) 

840 

841 return self._restore_object_instance(obj, cls, class_name) 

842 

843 def _restore_function(self, obj: dict[str, Any]) -> Any: 

844 return util.loadclass(obj[tags.FUNCTION], classes=self._classes) 

845 

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() 

851 

852 def _restore_dict(self, obj: dict[str, Any]) -> dict[str, Any]: 

853 data = {} 

854 self._mkref(data) 

855 

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)) 

868 

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 

894 

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 () 

900 

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 

949 

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)