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

514 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 isinstance(k, str): 

675 continue 

676 if not k.startswith("__"): 

677 try: 

678 setattr(instance, k, value) 

679 except KeyError: 

680 # certain numpy objects require us to prepend a _ to the var 

681 # this should go in the np handler but I think this could be 

682 # useful for other code 

683 setattr(instance, f"_{k}", value) 

684 except dataclasses.FrozenInstanceError: 

685 # issue #240 

686 # i think this is the only way to set frozen dataclass attrs 

687 object.__setattr__(instance, k, value) 

688 except AttributeError: 

689 # some objects raise this for read-only attributes (#422) (#478) 

690 if ( 

691 hasattr(instance, "__slots__") 

692 and not len(instance.__slots__) 

693 # we have to handle this separately because of +483 

694 and issubclass(instance.__class__, (int, str)) 

695 and self.handle_readonly 

696 ): 

697 continue 

698 raise 

699 else: 

700 setattr(instance, f"_{instance.__class__.__name__}{k}", value) 

701 

702 # This instance has an instance variable named `k` that is 

703 # currently a proxy and must be replaced 

704 if isinstance(value, _Proxy): 

705 self._proxies.append((instance, k, value, method)) 

706 

707 if deferred: 

708 # SQLAlchemy Immutable mappings must be constructed in one shot 

709 instance = instance.__class__(deferred) 

710 

711 return instance 

712 

713 def _restore_state(self, obj: dict[str, Any], instance: Any) -> Any: 

714 state = self._restore(obj[tags.STATE]) 

715 has_slots = ( 

716 isinstance(state, tuple) and len(state) == 2 and isinstance(state[1], dict) 

717 ) 

718 has_slots_and_dict = has_slots and isinstance(state[0], dict) 

719 if hasattr(instance, "__setstate__"): 

720 instance.__setstate__(state) 

721 elif isinstance(state, dict): 

722 # implements described default handling 

723 # of state for object with instance dict 

724 # and no slots 

725 instance = self._restore_from_dict( 

726 state, instance, ignorereserved=False, restore_dict_items=False 

727 ) 

728 elif has_slots: 

729 instance = self._restore_from_dict( 

730 state[1], instance, ignorereserved=False, restore_dict_items=False 

731 ) 

732 if has_slots_and_dict: 

733 instance = self._restore_from_dict( 

734 state[0], instance, ignorereserved=False, restore_dict_items=False 

735 ) 

736 elif not hasattr(instance, "__getnewargs__") and not hasattr( 

737 instance, "__getnewargs_ex__" 

738 ): 

739 # __setstate__ is not implemented so that means that the best 

740 # we can do is return the result of __getstate__() rather than 

741 # return an empty shell of an object. 

742 # However, if there were newargs, it's not an empty shell 

743 instance = state 

744 return instance 

745 

746 def _restore_object_instance_variables( 

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

748 ) -> Any: 

749 instance = self._restore_from_dict(obj, instance) 

750 

751 # Handle list and set subclasses 

752 if has_tag(obj, tags.SEQ): 

753 if hasattr(instance, "append"): 

754 for v in obj[tags.SEQ]: 

755 instance.append(self._restore(v)) 

756 elif hasattr(instance, "add"): 

757 for v in obj[tags.SEQ]: 

758 instance.add(self._restore(v)) 

759 

760 if has_tag(obj, tags.STATE): 

761 instance = self._restore_state(obj, instance) 

762 

763 return instance 

764 

765 def _restore_object_instance( 

766 self, obj: dict[str, Any], cls: type, class_name: str = "" 

767 ) -> Any: 

768 # This is a placeholder proxy object which allows child objects to 

769 # reference the parent object before it has been instantiated. 

770 proxy = _Proxy() 

771 self._mkref(proxy) 

772 

773 # An object can install itself as its own factory, so load the factory 

774 # after the instance is available for referencing. 

775 factory = self._loadfactory(obj) 

776 

777 if has_tag(obj, tags.NEWARGSEX): 

778 args, kwargs = obj[tags.NEWARGSEX] 

779 else: 

780 args = getargs(obj, classes=self._classes) 

781 kwargs = {} 

782 if args: 

783 args = self._restore(args) 

784 if kwargs: 

785 kwargs = self._restore(kwargs) 

786 

787 is_oldstyle = not (isinstance(cls, type) or getattr(cls, "__meta__", None)) 

788 try: 

789 if not is_oldstyle and hasattr(cls, "__new__"): 

790 # new style classes 

791 if factory: 

792 instance = cls.__new__(cls, factory, *args, **kwargs) 

793 instance.default_factory = factory 

794 else: 

795 instance = cls.__new__(cls, *args, **kwargs) 

796 else: 

797 instance = object.__new__(cls) 

798 except TypeError: # old-style classes 

799 is_oldstyle = True 

800 

801 if is_oldstyle: 

802 try: 

803 instance = cls(*args) 

804 except TypeError: # fail gracefully 

805 try: 

806 instance = make_blank_classic(cls) 

807 except Exception: # ruff: ignore[BLE001] 

808 self._process_missing(class_name) 

809 return self._mkref(obj) 

810 

811 proxy.reset(instance) 

812 self._swapref(proxy, instance) 

813 

814 if isinstance(instance, tuple): 

815 return instance 

816 

817 instance = self._restore_object_instance_variables(obj, instance) 

818 

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

820 instance.default_factory, _Proxy 

821 ): 

822 instance.default_factory = instance.default_factory.get() 

823 

824 return instance 

825 

826 def _restore_object(self, obj: dict[str, Any]) -> Any: 

827 class_name = obj[tags.OBJECT] 

828 cls = util.loadclass(class_name, classes=self._classes) 

829 handler = handlers.get(cls, handlers.get(class_name)) # type: ignore[arg-type] 

830 if handler is not None: # custom handler 

831 proxy = _Proxy() 

832 self._mkref(proxy) 

833 handler_instance = handler(self) 

834 instance = self._call_handler_restore(handler_instance, obj) 

835 proxy.reset(instance) 

836 self._swapref(proxy, instance) 

837 return instance 

838 

839 if cls is None: 

840 self._process_missing(class_name) 

841 return self._mkref(obj) 

842 

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

844 

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

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

847 

848 def _restore_set(self, obj: dict[str, Any]) -> set[Any]: 

849 try: 

850 return {self._restore(v) for v in obj[tags.SET]} 

851 except TypeError: 

852 return set() 

853 

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

855 data = {} 

856 self._mkref(data) 

857 

858 # If we are decoding dicts that can have non-string keys then we 

859 # need to do a two-phase decode where the non-string keys are 

860 # processed last. This ensures a deterministic order when 

861 # assigning object IDs for references. 

862 if self.keys: 

863 # Phase 1: regular non-special keys. 

864 for k, v in util.items(obj): 

865 if _is_json_key(k): 

866 continue 

867 data[k] = result = self._restore(v) 

868 if isinstance(result, _Proxy): 

869 self._proxies.append((data, k, result, _obj_setvalue)) 

870 

871 # Phase 2: object keys only. 

872 for k, v in util.items(obj): 

873 if not _is_json_key(k): 

874 continue 

875 restored_key = self._restore_pickled_key(k) 

876 result = self._restore(v) 

877 try: 

878 data[restored_key] = result 

879 except TypeError: # fail gracefully 

880 # The encoder can never emit an unhashable key, so if this 

881 # is triggered then we're dealing with hand-crafted input. 

882 # Keep the raw json:// key rather than failing the whole decode 

883 data[k] = result 

884 else: 

885 k = restored_key 

886 # k is currently a proxy and must be replaced 

887 if isinstance(result, _Proxy): 

888 self._proxies.append((data, k, result, _obj_setvalue)) 

889 else: 

890 # No special keys, thus we don't need to restore the keys either. 

891 for k, v in util.items(obj): 

892 data[k] = result = self._restore(v) 

893 if isinstance(result, _Proxy): 

894 self._proxies.append((data, k, result, _obj_setvalue)) 

895 return data 

896 

897 def _restore_tuple(self, obj: dict[str, Any]) -> tuple[Any, ...]: 

898 try: 

899 return tuple(self._restore(v) for v in obj[tags.TUPLE]) 

900 except TypeError: 

901 return () 

902 

903 def _restore_tags( 

904 self, obj: Any, _passthrough: Callable[[Any], Any] = _passthrough 

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

906 """Return the restoration function for the specified object""" 

907 try: 

908 if not tags.RESERVED <= set(obj) and type(obj) not in (list, dict): 

909 return _passthrough 

910 except TypeError: 

911 pass 

912 if type(obj) is dict: 

913 if tags.TUPLE in obj: 

914 restore = self._restore_tuple 

915 elif tags.SET in obj: 

916 restore = self._restore_set # type: ignore[assignment] 

917 elif tags.B64 in obj: 

918 restore = self._restore_base64 # type: ignore[assignment] 

919 elif tags.B85 in obj: 

920 restore = self._restore_base85 # type: ignore[assignment] 

921 elif tags.BYTEARRAY in obj: 

922 restore = self._restore_bytearray # type: ignore[assignment] 

923 elif tags.MEMORYVIEW in obj: 

924 restore = self._restore_memoryview # type: ignore[assignment] 

925 elif tags.ID in obj: 

926 restore = self._restore_id 

927 elif tags.ITERATOR in obj: 

928 restore = self._restore_iterator # type: ignore[assignment] 

929 elif tags.OBJECT in obj: 

930 restore = self._restore_object 

931 elif tags.TYPE in obj: 

932 restore = self._restore_type 

933 elif tags.REDUCE in obj: 

934 restore = self._restore_reduce 

935 elif tags.FUNCTION in obj: 

936 restore = self._restore_function 

937 elif tags.MODULE in obj: 

938 restore = self._restore_module 

939 elif tags.REPR in obj: 

940 if self.safe: 

941 restore = self._restore_repr_safe 

942 else: 

943 restore = self._restore_repr 

944 else: 

945 restore = self._restore_dict # type: ignore[assignment] 

946 elif type(obj) is list: 

947 restore = self._restore_list # type: ignore[assignment] 

948 else: 

949 restore = _passthrough # type: ignore[assignment] 

950 return restore 

951 

952 def _call_handler_restore( 

953 self, handler: handlers.BaseHandler, obj: dict[str, Any] 

954 ) -> Any: 

955 kwargs: dict[str, Any] = {} 

956 if ( 

957 self.handler_context is not None 

958 and handlers.handler_accepts_handler_context(handler.restore) 

959 ): 

960 kwargs["handler_context"] = self.handler_context 

961 return handler.restore(obj, **kwargs)