Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jsonpickle/unpickler.py: 69%

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

513 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 JSONBackend, 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 backend: JSONBackend | None = None, 

24 # we get a lot of errors when typing with TypeVar 

25 context: "Unpickler | None" = None, 

26 keys: bool = False, 

27 reset: bool = True, 

28 safe: bool = True, 

29 classes: ClassesType | None = None, 

30 v1_decode: bool = False, 

31 on_missing: MissingHandler = "ignore", 

32 handle_readonly: bool = False, 

33 handler_context: Any = None, 

34) -> Any: 

35 """Convert a JSON string into a Python object. 

36 

37 :param backend: If set to an instance of jsonpickle.backend.JSONBackend, jsonpickle 

38 will use that backend for deserialization. 

39 

40 :param context: Supply a pre-built Pickler or Unpickler object to the 

41 `jsonpickle.encode` and `jsonpickle.decode` machinery instead 

42 of creating a new instance. The `context` represents the currently 

43 active Pickler and Unpickler objects when custom handlers are 

44 invoked by jsonpickle. 

45 

46 :param keys: If set to True then jsonpickle will decode non-string dictionary keys 

47 into python objects via the jsonpickle protocol. 

48 

49 :param reset: Custom pickle handlers that use the `Pickler.flatten` method or 

50 `jsonpickle.encode` function must call `encode` with `reset=False` 

51 in order to retain object references during pickling. 

52 This flag is not typically used outside of a custom handler or 

53 `__getstate__` implementation. 

54 

55 :param safe: If set to ``False``, use of ``eval()`` for backwards-compatible (pre-0.7.0) 

56 deserialization of repr-serialized objects is enabled. Defaults to ``True``. 

57 The default value was ``False`` in jsonpickle v3 and changed to ``True`` in jsonpickle v4. 

58 

59 .. warning:: 

60 

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

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

63 

64 :param classes: If set to a single class, or a sequence (list, set, tuple) of 

65 classes, then the classes will be made available when constructing objects. 

66 If set to a dictionary of class names to class objects, the class object 

67 will be provided to jsonpickle to deserialize the class name into. 

68 This can be used to give jsonpickle access to local classes that are not 

69 available through the global module import scope, and the dict method can 

70 be used to deserialize encoded objects into a new class. An example of using 

71 this argument can be found in examples/changing_class_path.py on GitHub. 

72 

73 :param v1_decode: If set to True it enables you to decode objects serialized in 

74 jsonpickle v1. Please do not attempt to re-encode the objects in the v1 format! 

75 Version 2's format fixes issue #255, and allows dictionary identity to be 

76 preserved through an encode/decode cycle. 

77 

78 :param on_missing: If set to 'error', it will raise an error if the class it's 

79 decoding is not found. If set to 'warn', it will warn you in said case. 

80 If set to a non-awaitable function, it will call said callback function 

81 with the class name (a string) as the only parameter. Strings passed to 

82 `on_missing` are lowercased automatically. 

83 

84 :param handle_readonly: If set to True, the Unpickler will handle objects encoded 

85 with 'handle_readonly' properly. Do not set this flag for objects not encoded 

86 with 'handle_readonly' set to True. 

87 

88 :param handler_context: 

89 Pass custom context to a custom handler. This can be used to customize 

90 behavior at runtime based off data. Defaults to ``None``. An example can 

91 be found in the examples/ directory on GitHub. 

92 

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

94 True 

95 >>> decode('36') 

96 36 

97 """ 

98 

99 if isinstance(on_missing, str): 

100 on_missing = on_missing.lower() 

101 elif not util._is_function(on_missing): 

102 warnings.warn( 

103 "Unpickler.on_missing must be a string or a function! It will be ignored!" 

104 ) 

105 

106 backend = backend or json 

107 is_ephemeral_context = context is None 

108 context = context or Unpickler( 

109 keys=keys, 

110 backend=backend, 

111 safe=safe, 

112 v1_decode=v1_decode, 

113 on_missing=on_missing, 

114 handle_readonly=handle_readonly, 

115 handler_context=handler_context, 

116 ) 

117 if handler_context is not None: 

118 context.handler_context = handler_context 

119 data = backend.decode(string) 

120 result = context.restore(data, reset=reset, classes=classes) 

121 if is_ephemeral_context: 

122 # Avoid holding onto references to external objects, which can 

123 # prevent garbage collection from occuring. 

124 context.reset() 

125 return result 

126 

127 

128def _safe_hasattr(obj: Any, attr: str) -> bool: 

129 """Workaround unreliable hasattr() availability on sqlalchemy objects""" 

130 try: 

131 object.__getattribute__(obj, attr) 

132 return True 

133 except AttributeError: 

134 return False 

135 

136 

137def _is_json_key(key: Any) -> bool: 

138 """Has this key a special object that has been encoded to JSON?""" 

139 return isinstance(key, str) and key.startswith(tags.JSON_KEY) 

140 

141 

142class _Proxy: 

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

144 

145 The `restore()` function has to solve a tricky problem when pickling 

146 objects with cyclical references -- the parent instance does not yet 

147 exist. 

148 

149 The problem is that `__getnewargs__()`, `__getstate__()`, custom handlers, 

150 and cyclical objects graphs are allowed to reference the yet-to-be-created 

151 object via the referencing machinery. 

152 

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

154 construction! 

155 

156 We solve this problem by placing dummy Proxy objects into the referencing 

157 machinery so that we can construct the child objects before constructing 

158 the parent. Objects are initially created with Proxy attribute values 

159 instead of real references. 

160 

161 We collect all objects that contain references to proxies and run 

162 a final sweep over them to swap in the real instance. This is done 

163 at the very end of the top-level `restore()`. 

164 

165 The `instance` attribute below is replaced with the real instance 

166 after `__new__()` has been used to construct the object and is used 

167 when swapping proxies with real instances. 

168 

169 """ 

170 

171 def __init__(self) -> None: 

172 self.instance = None 

173 

174 def get(self) -> Any: 

175 return self.instance 

176 

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

178 self.instance = instance 

179 

180 

181class _IDProxy(_Proxy): 

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

183 self._index = index 

184 self._objs = objs 

185 

186 def get(self) -> Any: 

187 try: 

188 return self._objs[self._index] 

189 except IndexError: 

190 return None 

191 

192 

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

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

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

196 

197 

198def _obj_setvalue(obj: Any, idx: Any, proxy: _Proxy) -> None: 

199 """Use obj[key] assignments to update a proxy entry""" 

200 obj[idx] = proxy.get() 

201 

202 

203def has_tag(obj: Any, tag: str) -> bool: 

204 """Helper class that tests to see if the obj is a dictionary 

205 and contains a particular key/tag. 

206 

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

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

209 True 

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

211 False 

212 

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

214 False 

215 

216 """ 

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

218 

219 

220def getargs(obj: dict[str, Any], classes: dict[str, type] | None = None) -> Any: 

221 """Return arguments suitable for __new__()""" 

222 # Let saved newargs take precedence over everything 

223 if has_tag(obj, tags.NEWARGSEX): 

224 raise ValueError("__newargs_ex__ returns both args and kwargs") 

225 

226 if has_tag(obj, tags.NEWARGS): 

227 return obj[tags.NEWARGS] 

228 

229 if has_tag(obj, tags.INITARGS): 

230 return obj[tags.INITARGS] 

231 

232 try: 

233 seq_list = obj[tags.SEQ] 

234 obj_dict = obj[tags.OBJECT] 

235 except KeyError: 

236 return [] 

237 typeref = util.loadclass(obj_dict, classes=classes) 

238 if not typeref: 

239 return [] 

240 if hasattr(typeref, "_fields"): 

241 if len(typeref._fields) == len(seq_list): 

242 return seq_list 

243 return [] 

244 

245 

246class _trivialclassic: 

247 """ 

248 A trivial class that can be instantiated with no args 

249 """ 

250 

251 

252def make_blank_classic(cls: type) -> Any: 

253 """ 

254 Implement the mandated strategy for dealing with classic classes 

255 which cannot be instantiated without __getinitargs__ because they 

256 take parameters 

257 """ 

258 instance = _trivialclassic() 

259 instance.__class__ = cls 

260 return instance 

261 

262 

263def loadrepr(reprstr: str) -> Any: 

264 """Returns an instance of the object from the object's repr() string. 

265 It involves the dynamic specification of code. 

266 

267 .. warning:: 

268 

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

270 

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

272 >>> obj.__class__.__name__ 

273 'datetime' 

274 

275 """ 

276 module, evalstr = reprstr.split("/") 

277 mylocals = locals() 

278 localname = module 

279 if "." in localname: 

280 localname = module.split(".", 1)[0] 

281 mylocals[localname] = __import__(module) 

282 return eval(evalstr, mylocals) 

283 

284 

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

286 """Returns a reference to a module. 

287 

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

289 >>> fn.__name__ 

290 'fromtimestamp' 

291 

292 """ 

293 module, identifier = module_str.split("/") 

294 try: 

295 result = __import__(module) 

296 except ImportError: 

297 return None 

298 identifier_parts = identifier.split(".") 

299 first_identifier = identifier_parts[0] 

300 if first_identifier != module and not module.startswith(f"{first_identifier}."): 

301 return None 

302 for name in identifier_parts[1:]: 

303 try: 

304 result = getattr(result, name) 

305 except AttributeError: 

306 return None 

307 return result 

308 

309 

310def has_tag_dict(obj: Any, tag: str) -> bool: 

311 """Helper class that tests to see if the obj is a dictionary 

312 and contains a particular key/tag. 

313 

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

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

316 True 

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

318 False 

319 

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

321 False 

322 

323 """ 

324 return tag in obj 

325 

326 

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

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

329 return value 

330 

331 

332class Unpickler: 

333 def __init__( 

334 self, 

335 backend: JSONBackend | None = None, 

336 keys: bool = False, 

337 safe: bool = True, 

338 v1_decode: bool = False, 

339 on_missing: MissingHandler = "ignore", 

340 handle_readonly: bool = False, 

341 handler_context: Any = None, 

342 ) -> None: 

343 self.backend = backend or json 

344 self.keys = keys 

345 self.safe = safe 

346 self.v1_decode = v1_decode 

347 self.on_missing = on_missing 

348 self.handle_readonly = handle_readonly 

349 # Custom context passed through to custom handlers, see #452 

350 self.handler_context = handler_context 

351 

352 self.reset() 

353 

354 def reset(self) -> None: 

355 """Resets the object's internal state.""" 

356 # Map reference names to object instances 

357 self._namedict = {} 

358 

359 # The stack of names traversed for child objects 

360 self._namestack = [] 

361 

362 # Map of objects to their index in the _objs list 

363 self._obj_to_idx = {} 

364 self._objs = [] 

365 self._proxies = [] 

366 

367 # Extra local classes not accessible globally 

368 self._classes = {} 

369 

370 def _swap_proxies(self) -> None: 

371 """Replace proxies with their corresponding instances""" 

372 for obj, attr, proxy, method in self._proxies: 

373 method(obj, attr, proxy) 

374 self._proxies = [] 

375 

376 def _restore( 

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

378 ) -> Any: 

379 # if obj isn't in these types, neither it nor nothing in it can have a tag 

380 # don't change the tuple of types to a set, it won't work with isinstance 

381 if not isinstance(obj, (str, list, dict, set, tuple)): 

382 restore = _passthrough 

383 else: 

384 restore = self._restore_tags(obj) 

385 return restore(obj) 

386 

387 def restore( 

388 self, obj: Any, reset: bool = True, classes: ClassesType | None = None 

389 ) -> Any: 

390 """Restores a flattened object to its original python state. 

391 

392 Simply returns any of the basic builtin types 

393 

394 >>> u = Unpickler() 

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

396 True 

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

398 True 

399 

400 """ 

401 if reset: 

402 self.reset() 

403 if classes: 

404 self.register_classes(classes) 

405 value = self._restore(obj) 

406 if reset: 

407 self._swap_proxies() 

408 return value 

409 

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

411 """Register one or more classes 

412 

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

414 

415 """ 

416 if isinstance(classes, (list, tuple, set)): 

417 for cls in classes: 

418 self.register_classes(cls) 

419 elif isinstance(classes, dict): 

420 self._classes.update( 

421 ( 

422 cls if isinstance(cls, str) else util.importable_name(cls), 

423 handler, 

424 ) 

425 for cls, handler in classes.items() 

426 ) 

427 else: 

428 self._classes[util.importable_name(classes)] = classes # type: ignore[arg-type] 

429 

430 def _restore_base64(self, obj: dict[str, Any]) -> bytes: 

431 try: 

432 return util.b64decode(obj[tags.B64].encode("utf-8")) 

433 except (AttributeError, UnicodeEncodeError): 

434 return b"" 

435 

436 def _restore_base85(self, obj: dict[str, Any]) -> bytes: 

437 try: 

438 return util.b85decode(obj[tags.B85].encode("utf-8")) 

439 except (AttributeError, UnicodeEncodeError): 

440 return b"" 

441 

442 def _refname(self) -> str: 

443 """Calculates the name of the current location in the JSON stack. 

444 

445 This is called as jsonpickle traverses the object structure to 

446 create references to previously-traversed objects. This allows 

447 cyclical data structures such as doubly-linked lists. 

448 jsonpickle ensures that duplicate python references to the same 

449 object results in only a single JSON object definition and 

450 special reference tags to represent each reference. 

451 

452 >>> u = Unpickler() 

453 >>> u._namestack = [] 

454 >>> u._refname() == '/' 

455 True 

456 >>> u._namestack = ['a'] 

457 >>> u._refname() == '/a' 

458 True 

459 >>> u._namestack = ['a', 'b'] 

460 >>> u._refname() == '/a/b' 

461 True 

462 

463 """ 

464 return "/" + "/".join(self._namestack) 

465 

466 def _mkref(self, obj: Any) -> Any: 

467 obj_id = id(obj) 

468 try: 

469 _ = self._obj_to_idx[obj_id] 

470 except KeyError: 

471 self._obj_to_idx[obj_id] = len(self._objs) 

472 self._objs.append(obj) 

473 # Backwards compatibility: old versions of jsonpickle 

474 # produced "py/ref" references. 

475 self._namedict[self._refname()] = obj 

476 return obj 

477 

478 def _restore_list(self, obj: list[Any]) -> list[Any]: 

479 parent = [] 

480 self._mkref(parent) 

481 children = [self._restore(v) for v in obj] 

482 parent.extend(children) 

483 method = _obj_setvalue 

484 proxies = [ 

485 (parent, idx, value, method) 

486 for idx, value in enumerate(parent) 

487 if isinstance(value, _Proxy) 

488 ] 

489 self._proxies.extend(proxies) 

490 return parent 

491 

492 def _restore_iterator(self, obj: dict[str, Any]) -> Iterator[Any]: 

493 try: 

494 return iter(self._restore_list(obj[tags.ITERATOR])) 

495 except TypeError: 

496 return iter([]) 

497 

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

499 proxy_id = id(proxy) 

500 instance_id = id(instance) 

501 

502 instance_index = self._obj_to_idx[proxy_id] 

503 self._obj_to_idx[instance_id] = instance_index 

504 del self._obj_to_idx[proxy_id] 

505 

506 self._objs[instance_index] = instance 

507 self._namedict[self._refname()] = instance 

508 

509 def _restore_reduce(self, obj: dict[str, Any]) -> Any: 

510 """ 

511 Supports restoring with all elements of __reduce__ as per pep 307. 

512 Assumes that iterator items (the last two) are represented as lists 

513 as per pickler implementation. 

514 """ 

515 proxy = _Proxy() 

516 self._mkref(proxy) 

517 try: 

518 reduce_val = list(map(self._restore, obj[tags.REDUCE])) 

519 except TypeError: 

520 result = [] 

521 proxy.reset(result) 

522 self._swapref(proxy, result) 

523 return result 

524 if len(reduce_val) < 5: 

525 reduce_val.extend([None] * (5 - len(reduce_val))) 

526 f, args, state, listitems, dictitems = reduce_val 

527 

528 if f == tags.NEWOBJ or getattr(f, "__name__", "") == "__newobj__": 

529 # mandated special case 

530 cls = args[0] 

531 if not isinstance(cls, type): 

532 cls = self._restore(cls) 

533 stage1 = cls.__new__(cls, *args[1:]) 

534 else: 

535 if not callable(f): 

536 result = [] 

537 proxy.reset(result) 

538 self._swapref(proxy, result) 

539 return result 

540 try: 

541 stage1 = f(*args) 

542 except TypeError: 

543 # this happens when there are missing kwargs and args don't match so we bypass 

544 # __init__ since the state dict will set all attributes immediately afterwards 

545 stage1 = f.__new__(f, *args) 

546 

547 if state: 

548 try: 

549 stage1.__setstate__(state) 

550 except AttributeError: 

551 # it's fine - we'll try the prescribed default methods 

552 try: 

553 # we can't do a straight update here because we 

554 # need object identity of the state dict to be 

555 # preserved so that _swap_proxies works out 

556 for k, v in stage1.__dict__.items(): 

557 state.setdefault(k, v) 

558 stage1.__dict__ = state 

559 except AttributeError: 

560 # next prescribed default 

561 try: 

562 for k, v in state.items(): 

563 setattr(stage1, k, v) 

564 except Exception: 

565 dict_state, slots_state = state 

566 if dict_state: 

567 stage1.__dict__.update(dict_state) 

568 if slots_state: 

569 for k, v in slots_state.items(): 

570 setattr(stage1, k, v) 

571 

572 if listitems: 

573 # should be lists if not None 

574 try: 

575 stage1.extend(listitems) 

576 except AttributeError: 

577 for x in listitems: 

578 stage1.append(x) 

579 

580 if dictitems: 

581 for k, v in dictitems: 

582 stage1.__setitem__(k, v) 

583 

584 proxy.reset(stage1) 

585 self._swapref(proxy, stage1) 

586 return stage1 

587 

588 def _restore_id(self, obj: dict[str, Any]) -> Any: 

589 try: 

590 idx = obj[tags.ID] 

591 return self._objs[idx] 

592 except IndexError: 

593 return _IDProxy(self._objs, idx) 

594 except TypeError: 

595 return None 

596 

597 def _restore_type(self, obj: dict[str, Any]) -> Any: 

598 typeref = util.loadclass(obj[tags.TYPE], classes=self._classes) 

599 if typeref is None: 

600 return obj 

601 return typeref 

602 

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

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

605 return self._mkref(new_obj) 

606 

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

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

609 return self._mkref(new_obj) 

610 

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

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

613 return self._mkref(obj) 

614 

615 def _loadfactory(self, obj: dict[str, Any]) -> Any | None: 

616 default_factory = None 

617 for key in (tags.DEFAULT_FACTORY, "default_factory"): 

618 try: 

619 default_factory = obj.pop(key) 

620 break 

621 except KeyError: 

622 continue 

623 if default_factory is None: 

624 return None 

625 return self._restore(default_factory) 

626 

627 def _process_missing(self, class_name: str) -> None: 

628 # most common case comes first 

629 if self.on_missing == "ignore": 

630 pass 

631 elif self.on_missing == "warn": 

632 warnings.warn("Unpickler._restore_object could not find %s!" % class_name) 

633 elif self.on_missing == "error": 

634 raise errors.ClassNotFoundError( 

635 "Unpickler.restore_object could not find %s!" % class_name 

636 ) 

637 elif util._is_function(self.on_missing): 

638 self.on_missing(class_name) # type: ignore[operator] 

639 

640 def _restore_pickled_key(self, key: str) -> Any: 

641 """Restore a possibly pickled key""" 

642 if _is_json_key(key): 

643 key = decode( 

644 key[len(tags.JSON_KEY) :], 

645 backend=self.backend, 

646 context=self, 

647 keys=True, 

648 reset=False, 

649 ) 

650 return key 

651 

652 def _restore_key_fn( 

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

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

655 """Return a callable that restores keys 

656 

657 This function is responsible for restoring non-string keys 

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

659 

660 """ 

661 # This function is called before entering a tight loop 

662 # where the returned function will be called. 

663 # We return a specific function after checking self.keys 

664 # instead of doing so in the body of the function to 

665 # avoid conditional branching inside a tight loop. 

666 if self.keys: 

667 restore_key = self._restore_pickled_key 

668 else: 

669 restore_key = _passthrough # type: ignore[assignment] 

670 return restore_key 

671 

672 def _restore_from_dict( 

673 self, 

674 obj: dict[str, Any], 

675 instance: Any, 

676 ignorereserved: bool = True, 

677 restore_dict_items: bool = True, 

678 ) -> Any: 

679 restore_key = self._restore_key_fn() 

680 method = _obj_setattr 

681 deferred = {} 

682 

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

684 # ignore the reserved attribute 

685 if ignorereserved and k in tags.RESERVED: 

686 continue 

687 if isinstance(k, (int, float)): 

688 str_k = k.__str__() 

689 else: 

690 str_k = k 

691 self._namestack.append(str_k) 

692 if restore_dict_items: 

693 k = restore_key(k) 

694 # step into the namespace 

695 value = self._restore(v) 

696 else: 

697 value = v 

698 if util._is_noncomplex(instance) or util._is_dictionary_subclass(instance): 

699 try: 

700 if k == "__dict__": 

701 setattr(instance, k, value) 

702 else: 

703 instance[k] = value 

704 except TypeError: 

705 # Immutable object, must be constructed in one shot 

706 if k != "__dict__": 

707 deferred[k] = value 

708 self._namestack.pop() 

709 continue 

710 else: 

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

712 try: 

713 setattr(instance, k, value) 

714 except KeyError: 

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

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

717 # useful for other code 

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

719 except dataclasses.FrozenInstanceError: 

720 # issue #240 

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

722 object.__setattr__(instance, k, value) 

723 except AttributeError as e: 

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

725 if ( 

726 hasattr(instance, "__slots__") 

727 and not len(instance.__slots__) 

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

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

730 and self.handle_readonly 

731 ): 

732 continue 

733 raise e 

734 else: 

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

736 

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

738 # currently a proxy and must be replaced 

739 if isinstance(value, _Proxy): 

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

741 

742 # step out 

743 self._namestack.pop() 

744 

745 if deferred: 

746 # SQLAlchemy Immutable mappings must be constructed in one shot 

747 instance = instance.__class__(deferred) 

748 

749 return instance 

750 

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

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

753 has_slots = ( 

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

755 ) 

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

757 if hasattr(instance, "__setstate__"): 

758 instance.__setstate__(state) 

759 elif isinstance(state, dict): 

760 # implements described default handling 

761 # of state for object with instance dict 

762 # and no slots 

763 instance = self._restore_from_dict( 

764 state, instance, ignorereserved=False, restore_dict_items=False 

765 ) 

766 elif has_slots: 

767 instance = self._restore_from_dict( 

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

769 ) 

770 if has_slots_and_dict: 

771 instance = self._restore_from_dict( 

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

773 ) 

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

775 instance, "__getnewargs_ex__" 

776 ): 

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

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

779 # return an empty shell of an object. 

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

781 instance = state 

782 return instance 

783 

784 def _restore_object_instance_variables( 

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

786 ) -> Any: 

787 instance = self._restore_from_dict(obj, instance) 

788 

789 # Handle list and set subclasses 

790 if has_tag(obj, tags.SEQ): 

791 if hasattr(instance, "append"): 

792 for v in obj[tags.SEQ]: 

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

794 elif hasattr(instance, "add"): 

795 for v in obj[tags.SEQ]: 

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

797 

798 if has_tag(obj, tags.STATE): 

799 instance = self._restore_state(obj, instance) 

800 

801 return instance 

802 

803 def _restore_object_instance( 

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

805 ) -> Any: 

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

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

808 proxy = _Proxy() 

809 self._mkref(proxy) 

810 

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

812 # after the instance is available for referencing. 

813 factory = self._loadfactory(obj) 

814 

815 if has_tag(obj, tags.NEWARGSEX): 

816 args, kwargs = obj[tags.NEWARGSEX] 

817 else: 

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

819 kwargs = {} 

820 if args: 

821 args = self._restore(args) 

822 if kwargs: 

823 kwargs = self._restore(kwargs) 

824 

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

826 try: 

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

828 # new style classes 

829 if factory: 

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

831 instance.default_factory = factory 

832 else: 

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

834 else: 

835 instance = object.__new__(cls) 

836 except TypeError: # old-style classes 

837 is_oldstyle = True 

838 

839 if is_oldstyle: 

840 try: 

841 instance = cls(*args) 

842 except TypeError: # fail gracefully 

843 try: 

844 instance = make_blank_classic(cls) 

845 except Exception: # fail gracefully 

846 self._process_missing(class_name) 

847 return self._mkref(obj) 

848 

849 proxy.reset(instance) 

850 self._swapref(proxy, instance) 

851 

852 if isinstance(instance, tuple): 

853 return instance 

854 

855 instance = self._restore_object_instance_variables(obj, instance) 

856 

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

858 instance.default_factory, _Proxy 

859 ): 

860 instance.default_factory = instance.default_factory.get() 

861 

862 return instance 

863 

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

865 class_name = obj[tags.OBJECT] 

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

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

868 if handler is not None: # custom handler 

869 proxy = _Proxy() 

870 self._mkref(proxy) 

871 handler_instance = handler(self) 

872 instance = self._call_handler_restore(handler_instance, obj) 

873 proxy.reset(instance) 

874 self._swapref(proxy, instance) 

875 return instance 

876 

877 if cls is None: 

878 self._process_missing(class_name) 

879 return self._mkref(obj) 

880 

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

882 

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

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

885 

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

887 try: 

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

889 except TypeError: 

890 return set() 

891 

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

893 data = {} 

894 if not self.v1_decode: 

895 self._mkref(data) 

896 

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

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

899 # processed last. This ensures a deterministic order when 

900 # assigning object IDs for references. 

901 if self.keys: 

902 # Phase 1: regular non-special keys. 

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

904 if _is_json_key(k): 

905 continue 

906 if isinstance(k, (int, float)): 

907 str_k = k.__str__() 

908 else: 

909 str_k = k 

910 self._namestack.append(str_k) 

911 data[k] = self._restore(v) 

912 

913 self._namestack.pop() 

914 

915 # Phase 2: object keys only. 

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

917 if not _is_json_key(k): 

918 continue 

919 self._namestack.append(k) 

920 

921 k = self._restore_pickled_key(k) 

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

923 # k is currently a proxy and must be replaced 

924 if isinstance(result, _Proxy): 

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

926 

927 self._namestack.pop() 

928 else: 

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

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

931 if isinstance(k, (int, float)): 

932 str_k = k.__str__() 

933 else: 

934 str_k = k 

935 self._namestack.append(str_k) 

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

937 if isinstance(result, _Proxy): 

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

939 self._namestack.pop() 

940 return data 

941 

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

943 try: 

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

945 except TypeError: 

946 return () 

947 

948 def _restore_tags( 

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

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

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

952 try: 

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

954 return _passthrough 

955 except TypeError: 

956 pass 

957 if type(obj) is dict: 

958 if tags.TUPLE in obj: 

959 restore = self._restore_tuple 

960 elif tags.SET in obj: 

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

962 elif tags.B64 in obj: 

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

964 elif tags.B85 in obj: 

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

966 elif tags.ID in obj: 

967 restore = self._restore_id 

968 elif tags.ITERATOR in obj: 

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

970 elif tags.OBJECT in obj: 

971 restore = self._restore_object 

972 elif tags.TYPE in obj: 

973 restore = self._restore_type 

974 elif tags.REDUCE in obj: 

975 restore = self._restore_reduce 

976 elif tags.FUNCTION in obj: 

977 restore = self._restore_function 

978 elif tags.MODULE in obj: 

979 restore = self._restore_module 

980 elif tags.REPR in obj: 

981 if self.safe: 

982 restore = self._restore_repr_safe 

983 else: 

984 restore = self._restore_repr 

985 else: 

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

987 elif type(obj) is list: 

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

989 else: 

990 restore = _passthrough # type: ignore[assignment] 

991 return restore 

992 

993 def _call_handler_restore( 

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

995 ) -> Any: 

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

997 if ( 

998 self.handler_context is not None 

999 and handlers.handler_accepts_handler_context(handler.restore) 

1000 ): 

1001 kwargs["handler_context"] = self.handler_context 

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