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

504 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 _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 

438 

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 

452 

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

458 

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

460 proxy_id = id(proxy) 

461 instance_id = id(instance) 

462 

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] 

466 

467 self._objs[instance_index] = instance 

468 

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 

487 

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) 

506 

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) 

534 

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) 

542 

543 if dictitems: 

544 for k, v in dictitems: 

545 stage1.__setitem__(k, v) 

546 

547 proxy.reset(stage1) 

548 self._swapref(proxy, stage1) 

549 return stage1 

550 

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 

559 

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 

565 

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

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

568 return self._mkref(new_obj) 

569 

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

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

572 return self._mkref(new_obj) 

573 

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

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

576 return self._mkref(obj) 

577 

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) 

589 

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] 

602 

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 

613 

614 def _restore_key_fn( 

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

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

617 """Return a callable that restores keys 

618 

619 This function is responsible for restoring non-string keys 

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

621 

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 

633 

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

644 

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) 

691 

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

696 

697 if deferred: 

698 # SQLAlchemy Immutable mappings must be constructed in one shot 

699 instance = instance.__class__(deferred) 

700 

701 return instance 

702 

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 

735 

736 def _restore_object_instance_variables( 

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

738 ) -> Any: 

739 instance = self._restore_from_dict(obj, instance) 

740 

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

749 

750 if has_tag(obj, tags.STATE): 

751 instance = self._restore_state(obj, instance) 

752 

753 return instance 

754 

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) 

762 

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) 

766 

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) 

776 

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 

790 

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) 

800 

801 proxy.reset(instance) 

802 self._swapref(proxy, instance) 

803 

804 if isinstance(instance, tuple): 

805 return instance 

806 

807 instance = self._restore_object_instance_variables(obj, instance) 

808 

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

810 instance.default_factory, _Proxy 

811 ): 

812 instance.default_factory = instance.default_factory.get() 

813 

814 return instance 

815 

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 

828 

829 if cls is None: 

830 self._process_missing(class_name) 

831 return self._mkref(obj) 

832 

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

834 

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

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

837 

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

843 

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

845 data = {} 

846 self._mkref(data) 

847 

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

860 

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 

886 

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

892 

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 

939 

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)