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

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

520 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 sys 

9import warnings 

10from types import ModuleType 

11from typing import ( 

12 Any, 

13 Callable, 

14 Dict, 

15 Iterator, 

16 List, 

17 Optional, 

18 Sequence, 

19 Set, 

20 Tuple, 

21 Type, 

22 Union, 

23) 

24 

25from . import errors, handlers, tags, util 

26from .backend import JSONBackend, json 

27 

28# class names to class objects (or sequence of classes) 

29ClassesType = Optional[Union[Type[Any], Dict[str, Type[Any]], Sequence[Type[Any]]]] 

30# handler for missing classes: either a policy name or a callback 

31MissingHandler = Union[str, Callable[[str], Any]] 

32 

33 

34def decode( 

35 string: str, 

36 backend: Optional[JSONBackend] = None, 

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

38 context: Optional["Unpickler"] = None, 

39 keys: bool = False, 

40 reset: bool = True, 

41 safe: bool = True, 

42 classes: Optional[ClassesType] = None, 

43 v1_decode: bool = False, 

44 on_missing: MissingHandler = 'ignore', 

45 handle_readonly: bool = False, 

46) -> Any: 

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

48 

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

50 will use that backend for deserialization. 

51 

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

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

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

55 active Pickler and Unpickler objects when custom handlers are 

56 invoked by jsonpickle. 

57 

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

59 into python objects via the jsonpickle protocol. 

60 

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

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

63 in order to retain object references during pickling. 

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

65 `__getstate__` implementation. 

66 

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

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

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

70 

71 .. warning:: 

72 

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

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

75 

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

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

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

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

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

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

82 be used to deserialize encoded objects into a new class. 

83 

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

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

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

87 preserved through an encode/decode cycle. 

88 

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

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

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

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

93 `on_missing` are lowercased automatically. 

94 

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

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

97 with 'handle_readonly' set to True. 

98 

99 

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

101 True 

102 >>> decode('36') 

103 36 

104 """ 

105 

106 if isinstance(on_missing, str): 

107 on_missing = on_missing.lower() 

108 elif not util._is_function(on_missing): 

109 warnings.warn( 

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

111 ) 

112 

113 backend = backend or json 

114 context = context or Unpickler( 

115 keys=keys, 

116 backend=backend, 

117 safe=safe, 

118 v1_decode=v1_decode, 

119 on_missing=on_missing, 

120 handle_readonly=handle_readonly, 

121 ) 

122 data = backend.decode(string) 

123 return context.restore(data, reset=reset, classes=classes) 

124 

125 

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

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

128 try: 

129 object.__getattribute__(obj, attr) 

130 return True 

131 except AttributeError: 

132 return False 

133 

134 

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

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

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

138 

139 

140class _Proxy: 

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

142 

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

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

145 exist. 

146 

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

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

149 object via the referencing machinery. 

150 

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

152 construction! 

153 

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

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

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

157 instead of real references. 

158 

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

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

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

162 

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

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

165 when swapping proxies with real instances. 

166 

167 """ 

168 

169 def __init__(self) -> None: 

170 self.instance = None 

171 

172 def get(self) -> Any: 

173 return self.instance 

174 

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

176 self.instance = instance 

177 

178 

179class _IDProxy(_Proxy): 

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

181 self._index = index 

182 self._objs = objs 

183 

184 def get(self) -> Any: 

185 try: 

186 return self._objs[self._index] 

187 except IndexError: 

188 return None 

189 

190 

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

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

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

194 

195 

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

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

198 obj[idx] = proxy.get() 

199 

200 

201def loadclass( 

202 module_and_name: str, classes: Optional[Dict[str, Type[Any]]] = None 

203) -> Optional[Union[Type[Any], ModuleType]]: 

204 """Loads the module and returns the class. 

205 

206 >>> cls = loadclass('datetime.datetime') 

207 >>> cls.__name__ 

208 'datetime' 

209 

210 >>> loadclass('does.not.exist') 

211 

212 >>> loadclass('builtins.int')() 

213 0 

214 

215 """ 

216 # Check if the class exists in a caller-provided scope 

217 if classes: 

218 try: 

219 return classes[module_and_name] 

220 except KeyError: 

221 # maybe they didn't provide a fully qualified path 

222 try: 

223 return classes[module_and_name.rsplit('.', 1)[-1]] 

224 except KeyError: 

225 pass 

226 # Otherwise, load classes from globally-accessible imports 

227 names = module_and_name.split('.') 

228 # First assume that everything up to the last dot is the module name, 

229 # then try other splits to handle classes that are defined within 

230 # classes 

231 for up_to in range(len(names) - 1, 0, -1): 

232 module = util.untranslate_module_name('.'.join(names[:up_to])) 

233 try: 

234 __import__(module) 

235 obj = sys.modules[module] 

236 for class_name in names[up_to:]: 

237 obj = getattr(obj, class_name) 

238 return obj 

239 except (AttributeError, ImportError, ValueError): 

240 continue 

241 # NoneType is a special case and can not be imported/created 

242 if module_and_name == "builtins.NoneType": 

243 return type(None) 

244 return None 

245 

246 

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

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

249 and contains a particular key/tag. 

250 

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

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

253 True 

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

255 False 

256 

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

258 False 

259 

260 """ 

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

262 

263 

264def getargs( 

265 obj: Dict[str, Any], classes: Optional[Dict[str, Type[Any]]] = None 

266) -> List[Any]: 

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

268 # Let saved newargs take precedence over everything 

269 if has_tag(obj, tags.NEWARGSEX): 

270 raise ValueError('__newargs_ex__ returns both args and kwargs') 

271 

272 if has_tag(obj, tags.NEWARGS): 

273 return obj[tags.NEWARGS] 

274 

275 if has_tag(obj, tags.INITARGS): 

276 return obj[tags.INITARGS] 

277 

278 try: 

279 seq_list = obj[tags.SEQ] 

280 obj_dict = obj[tags.OBJECT] 

281 except KeyError: 

282 return [] 

283 typeref = loadclass(obj_dict, classes=classes) 

284 if not typeref: 

285 return [] 

286 if hasattr(typeref, '_fields'): 

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

288 return seq_list 

289 return [] 

290 

291 

292class _trivialclassic: 

293 """ 

294 A trivial class that can be instantiated with no args 

295 """ 

296 

297 

298def make_blank_classic(cls: Type[Any]) -> _trivialclassic: 

299 """ 

300 Implement the mandated strategy for dealing with classic classes 

301 which cannot be instantiated without __getinitargs__ because they 

302 take parameters 

303 """ 

304 instance = _trivialclassic() 

305 instance.__class__ = cls 

306 return instance 

307 

308 

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

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

311 It involves the dynamic specification of code. 

312 

313 .. warning:: 

314 

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

316 

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

318 >>> obj.__class__.__name__ 

319 'datetime' 

320 

321 """ 

322 module, evalstr = reprstr.split('/') 

323 mylocals = locals() 

324 localname = module 

325 if '.' in localname: 

326 localname = module.split('.', 1)[0] 

327 mylocals[localname] = __import__(module) 

328 return eval(evalstr, mylocals) 

329 

330 

331def _loadmodule(module_str: str) -> Optional[Any]: 

332 """Returns a reference to a module. 

333 

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

335 >>> fn.__name__ 

336 'fromtimestamp' 

337 

338 """ 

339 module, identifier = module_str.split('/') 

340 try: 

341 result = __import__(module) 

342 except ImportError: 

343 return None 

344 identifier_parts = identifier.split('.') 

345 first_identifier = identifier_parts[0] 

346 if first_identifier != module and not module.startswith(f'{first_identifier}.'): 

347 return None 

348 for name in identifier_parts[1:]: 

349 try: 

350 result = getattr(result, name) 

351 except AttributeError: 

352 return None 

353 return result 

354 

355 

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

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

358 and contains a particular key/tag. 

359 

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

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

362 True 

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

364 False 

365 

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

367 False 

368 

369 """ 

370 return tag in obj 

371 

372 

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

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

375 return value 

376 

377 

378class Unpickler: 

379 def __init__( 

380 self, 

381 backend: Optional[JSONBackend] = None, 

382 keys: bool = False, 

383 safe: bool = True, 

384 v1_decode: bool = False, 

385 on_missing: MissingHandler = 'ignore', 

386 handle_readonly: bool = False, 

387 ) -> None: 

388 self.backend = backend or json 

389 self.keys = keys 

390 self.safe = safe 

391 self.v1_decode = v1_decode 

392 self.on_missing = on_missing 

393 self.handle_readonly = handle_readonly 

394 

395 self.reset() 

396 

397 def reset(self) -> None: 

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

399 # Map reference names to object instances 

400 self._namedict = {} 

401 

402 # The stack of names traversed for child objects 

403 self._namestack = [] 

404 

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

406 self._obj_to_idx = {} 

407 self._objs = [] 

408 self._proxies = [] 

409 

410 # Extra local classes not accessible globally 

411 self._classes = {} 

412 

413 def _swap_proxies(self) -> None: 

414 """Replace proxies with their corresponding instances""" 

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

416 method(obj, attr, proxy) 

417 self._proxies = [] 

418 

419 def _restore( 

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

421 ) -> Any: 

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

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

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

425 restore = _passthrough 

426 else: 

427 restore = self._restore_tags(obj) 

428 return restore(obj) 

429 

430 def restore( 

431 self, obj: Any, reset: bool = True, classes: Optional[ClassesType] = None 

432 ) -> Any: 

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

434 

435 Simply returns any of the basic builtin types 

436 

437 >>> u = Unpickler() 

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

439 True 

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

441 True 

442 

443 """ 

444 if reset: 

445 self.reset() 

446 if classes: 

447 self.register_classes(classes) 

448 value = self._restore(obj) 

449 if reset: 

450 self._swap_proxies() 

451 return value 

452 

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

454 """Register one or more classes 

455 

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

457 

458 """ 

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

460 for cls in classes: 

461 self.register_classes(cls) 

462 elif isinstance(classes, dict): 

463 self._classes.update( 

464 ( 

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

466 handler, 

467 ) 

468 for cls, handler in classes.items() 

469 ) 

470 else: 

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

472 

473 def _restore_base64(self, obj: Dict[str, Any]) -> bytes: 

474 try: 

475 return util.b64decode(obj[tags.B64].encode('utf-8')) 

476 except (AttributeError, UnicodeEncodeError): 

477 return b'' 

478 

479 def _restore_base85(self, obj: Dict[str, Any]) -> bytes: 

480 try: 

481 return util.b85decode(obj[tags.B85].encode('utf-8')) 

482 except (AttributeError, UnicodeEncodeError): 

483 return b'' 

484 

485 def _refname(self) -> str: 

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

487 

488 This is called as jsonpickle traverses the object structure to 

489 create references to previously-traversed objects. This allows 

490 cyclical data structures such as doubly-linked lists. 

491 jsonpickle ensures that duplicate python references to the same 

492 object results in only a single JSON object definition and 

493 special reference tags to represent each reference. 

494 

495 >>> u = Unpickler() 

496 >>> u._namestack = [] 

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

498 True 

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

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

501 True 

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

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

504 True 

505 

506 """ 

507 return '/' + '/'.join(self._namestack) 

508 

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

510 obj_id = id(obj) 

511 try: 

512 _ = self._obj_to_idx[obj_id] 

513 except KeyError: 

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

515 self._objs.append(obj) 

516 # Backwards compatibility: old versions of jsonpickle 

517 # produced "py/ref" references. 

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

519 return obj 

520 

521 def _restore_list(self, obj: List[Any]) -> List[Any]: 

522 parent = [] 

523 self._mkref(parent) 

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

525 parent.extend(children) 

526 method = _obj_setvalue 

527 proxies = [ 

528 (parent, idx, value, method) 

529 for idx, value in enumerate(parent) 

530 if isinstance(value, _Proxy) 

531 ] 

532 self._proxies.extend(proxies) 

533 return parent 

534 

535 def _restore_iterator(self, obj: Dict[str, Any]) -> Iterator[Any]: 

536 try: 

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

538 except TypeError: 

539 return iter([]) 

540 

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

542 proxy_id = id(proxy) 

543 instance_id = id(instance) 

544 

545 instance_index = self._obj_to_idx[proxy_id] 

546 self._obj_to_idx[instance_id] = instance_index 

547 del self._obj_to_idx[proxy_id] 

548 

549 self._objs[instance_index] = instance 

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

551 

552 def _restore_reduce(self, obj: Dict[str, Any]) -> Any: 

553 """ 

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

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

556 as per pickler implementation. 

557 """ 

558 proxy = _Proxy() 

559 self._mkref(proxy) 

560 try: 

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

562 except TypeError: 

563 result = [] 

564 proxy.reset(result) 

565 self._swapref(proxy, result) 

566 return result 

567 if len(reduce_val) < 5: 

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

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

570 

571 if f == tags.NEWOBJ or getattr(f, '__name__', '') == '__newobj__': 

572 # mandated special case 

573 cls = args[0] 

574 if not isinstance(cls, type): 

575 cls = self._restore(cls) 

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

577 else: 

578 if not callable(f): 

579 result = [] 

580 proxy.reset(result) 

581 self._swapref(proxy, result) 

582 return result 

583 try: 

584 stage1 = f(*args) 

585 except TypeError: 

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

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

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

589 

590 if state: 

591 try: 

592 stage1.__setstate__(state) 

593 except AttributeError: 

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

595 try: 

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

597 # need object identity of the state dict to be 

598 # preserved so that _swap_proxies works out 

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

600 state.setdefault(k, v) 

601 stage1.__dict__ = state 

602 except AttributeError: 

603 # next prescribed default 

604 try: 

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

606 setattr(stage1, k, v) 

607 except Exception: 

608 dict_state, slots_state = state 

609 if dict_state: 

610 stage1.__dict__.update(dict_state) 

611 if slots_state: 

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

613 setattr(stage1, k, v) 

614 

615 if listitems: 

616 # should be lists if not None 

617 try: 

618 stage1.extend(listitems) 

619 except AttributeError: 

620 for x in listitems: 

621 stage1.append(x) 

622 

623 if dictitems: 

624 for k, v in dictitems: 

625 stage1.__setitem__(k, v) 

626 

627 proxy.reset(stage1) 

628 self._swapref(proxy, stage1) 

629 return stage1 

630 

631 def _restore_id(self, obj: Dict[str, Any]) -> Any: 

632 try: 

633 idx = obj[tags.ID] 

634 return self._objs[idx] 

635 except IndexError: 

636 return _IDProxy(self._objs, idx) 

637 except TypeError: 

638 return None 

639 

640 def _restore_type(self, obj: Dict[str, Any]) -> Any: 

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

642 if typeref is None: 

643 return obj 

644 return typeref 

645 

646 def _restore_module(self, obj: Dict[str, Any]) -> Any: 

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

648 return self._mkref(new_obj) 

649 

650 def _restore_repr_safe(self, obj: Dict[str, Any]) -> Any: 

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

652 return self._mkref(new_obj) 

653 

654 def _restore_repr(self, obj: Dict[str, Any]) -> Any: 

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

656 return self._mkref(obj) 

657 

658 def _loadfactory(self, obj: Dict[str, Any]) -> Optional[Any]: 

659 try: 

660 default_factory = obj['default_factory'] 

661 except KeyError: 

662 return None 

663 del obj['default_factory'] 

664 return self._restore(default_factory) 

665 

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

667 # most common case comes first 

668 if self.on_missing == 'ignore': 

669 pass 

670 elif self.on_missing == 'warn': 

671 warnings.warn('Unpickler._restore_object could not find %s!' % class_name) 

672 elif self.on_missing == 'error': 

673 raise errors.ClassNotFoundError( 

674 'Unpickler.restore_object could not find %s!' % class_name # type: ignore[arg-type] 

675 ) 

676 elif util._is_function(self.on_missing): 

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

678 

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

680 """Restore a possibly pickled key""" 

681 if _is_json_key(key): 

682 key = decode( 

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

684 backend=self.backend, 

685 context=self, 

686 keys=True, 

687 reset=False, 

688 ) 

689 return key 

690 

691 def _restore_key_fn( 

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

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

694 """Return a callable that restores keys 

695 

696 This function is responsible for restoring non-string keys 

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

698 

699 """ 

700 # This function is called before entering a tight loop 

701 # where the returned function will be called. 

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

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

704 # avoid conditional branching inside a tight loop. 

705 if self.keys: 

706 restore_key = self._restore_pickled_key 

707 else: 

708 restore_key = _passthrough # type: ignore[assignment] 

709 return restore_key 

710 

711 def _restore_from_dict( 

712 self, 

713 obj: Dict[str, Any], 

714 instance: Any, 

715 ignorereserved: bool = True, 

716 restore_dict_items: bool = True, 

717 ) -> Any: 

718 restore_key = self._restore_key_fn() 

719 method = _obj_setattr 

720 deferred = {} 

721 

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

723 # ignore the reserved attribute 

724 if ignorereserved and k in tags.RESERVED: 

725 continue 

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

727 str_k = k.__str__() 

728 else: 

729 str_k = k 

730 self._namestack.append(str_k) 

731 if restore_dict_items: 

732 k = restore_key(k) 

733 # step into the namespace 

734 value = self._restore(v) 

735 else: 

736 value = v 

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

738 try: 

739 if k == '__dict__': 

740 setattr(instance, k, value) 

741 else: 

742 instance[k] = value 

743 except TypeError: 

744 # Immutable object, must be constructed in one shot 

745 if k != '__dict__': 

746 deferred[k] = value 

747 self._namestack.pop() 

748 continue 

749 else: 

750 if not k.startswith('__'): 

751 try: 

752 setattr(instance, k, value) 

753 except KeyError: 

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

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

756 # useful for other code 

757 setattr(instance, f'_{k}', value) 

758 except dataclasses.FrozenInstanceError: 

759 # issue #240 

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

761 object.__setattr__(instance, k, value) 

762 except AttributeError as e: 

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

764 if ( 

765 hasattr(instance, '__slots__') 

766 and not len(instance.__slots__) 

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

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

769 and self.handle_readonly 

770 ): 

771 continue 

772 raise e 

773 else: 

774 setattr(instance, f'_{instance.__class__.__name__}{k}', value) 

775 

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

777 # currently a proxy and must be replaced 

778 if isinstance(value, _Proxy): 

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

780 

781 # step out 

782 self._namestack.pop() 

783 

784 if deferred: 

785 # SQLAlchemy Immutable mappings must be constructed in one shot 

786 instance = instance.__class__(deferred) 

787 

788 return instance 

789 

790 def _restore_state(self, obj: Dict[str, Any], instance: Any) -> Any: 

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

792 has_slots = ( 

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

794 ) 

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

796 if hasattr(instance, '__setstate__'): 

797 instance.__setstate__(state) 

798 elif isinstance(state, dict): 

799 # implements described default handling 

800 # of state for object with instance dict 

801 # and no slots 

802 instance = self._restore_from_dict( 

803 state, instance, ignorereserved=False, restore_dict_items=False 

804 ) 

805 elif has_slots: 

806 instance = self._restore_from_dict( 

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

808 ) 

809 if has_slots_and_dict: 

810 instance = self._restore_from_dict( 

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

812 ) 

813 elif not hasattr(instance, '__getnewargs__') and not hasattr( 

814 instance, '__getnewargs_ex__' 

815 ): 

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

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

818 # return an empty shell of an object. 

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

820 instance = state 

821 return instance 

822 

823 def _restore_object_instance_variables( 

824 self, obj: Dict[str, Any], instance: Any 

825 ) -> Any: 

826 instance = self._restore_from_dict(obj, instance) 

827 

828 # Handle list and set subclasses 

829 if has_tag(obj, tags.SEQ): 

830 if hasattr(instance, 'append'): 

831 for v in obj[tags.SEQ]: 

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

833 elif hasattr(instance, 'add'): 

834 for v in obj[tags.SEQ]: 

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

836 

837 if has_tag(obj, tags.STATE): 

838 instance = self._restore_state(obj, instance) 

839 

840 return instance 

841 

842 def _restore_object_instance( 

843 self, obj: Dict[str, Any], cls: Type[Any], class_name: str = '' 

844 ) -> Any: 

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

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

847 proxy = _Proxy() 

848 self._mkref(proxy) 

849 

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

851 # after the instance is available for referencing. 

852 factory = self._loadfactory(obj) 

853 

854 if has_tag(obj, tags.NEWARGSEX): 

855 args, kwargs = obj[tags.NEWARGSEX] 

856 else: 

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

858 kwargs = {} 

859 if args: 

860 args = self._restore(args) 

861 if kwargs: 

862 kwargs = self._restore(kwargs) 

863 

864 is_oldstyle = not (isinstance(cls, type) or getattr(cls, '__meta__', None)) 

865 try: 

866 if not is_oldstyle and hasattr(cls, '__new__'): 

867 # new style classes 

868 if factory: 

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

870 instance.default_factory = factory 

871 else: 

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

873 else: 

874 instance = object.__new__(cls) 

875 except TypeError: # old-style classes 

876 is_oldstyle = True 

877 

878 if is_oldstyle: 

879 try: 

880 instance = cls(*args) 

881 except TypeError: # fail gracefully 

882 try: 

883 instance = make_blank_classic(cls) 

884 except Exception: # fail gracefully 

885 self._process_missing(class_name) 

886 return self._mkref(obj) 

887 

888 proxy.reset(instance) 

889 self._swapref(proxy, instance) 

890 

891 if isinstance(instance, tuple): 

892 return instance 

893 

894 instance = self._restore_object_instance_variables(obj, instance) 

895 

896 if _safe_hasattr(instance, 'default_factory') and isinstance( 

897 instance.default_factory, _Proxy 

898 ): 

899 instance.default_factory = instance.default_factory.get() 

900 

901 return instance 

902 

903 def _restore_object(self, obj: Dict[str, Any]) -> Any: 

904 class_name = obj[tags.OBJECT] 

905 cls = loadclass(class_name, classes=self._classes) 

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

907 if handler is not None: # custom handler 

908 proxy = _Proxy() 

909 self._mkref(proxy) 

910 instance = handler(self).restore(obj) 

911 proxy.reset(instance) 

912 self._swapref(proxy, instance) 

913 return instance 

914 

915 if cls is None: 

916 self._process_missing(class_name) 

917 return self._mkref(obj) 

918 

919 return self._restore_object_instance(obj, cls, class_name) # type: ignore[arg-type] 

920 

921 def _restore_function(self, obj: Dict[str, Any]) -> Any: 

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

923 

924 def _restore_set(self, obj: Dict[str, Any]) -> Set[Any]: 

925 try: 

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

927 except TypeError: 

928 return set() 

929 

930 def _restore_dict(self, obj: Dict[str, Any]) -> Dict[str, Any]: 

931 data = {} 

932 if not self.v1_decode: 

933 self._mkref(data) 

934 

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

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

937 # processed last. This ensures a deterministic order when 

938 # assigning object IDs for references. 

939 if self.keys: 

940 # Phase 1: regular non-special keys. 

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

942 if _is_json_key(k): 

943 continue 

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

945 str_k = k.__str__() 

946 else: 

947 str_k = k 

948 self._namestack.append(str_k) 

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

950 

951 self._namestack.pop() 

952 

953 # Phase 2: object keys only. 

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

955 if not _is_json_key(k): 

956 continue 

957 self._namestack.append(k) 

958 

959 k = self._restore_pickled_key(k) 

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

961 # k is currently a proxy and must be replaced 

962 if isinstance(result, _Proxy): 

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

964 

965 self._namestack.pop() 

966 else: 

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

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

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

970 str_k = k.__str__() 

971 else: 

972 str_k = k 

973 self._namestack.append(str_k) 

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

975 if isinstance(result, _Proxy): 

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

977 self._namestack.pop() 

978 return data 

979 

980 def _restore_tuple(self, obj: Dict[str, Any]) -> Tuple[Any, ...]: 

981 try: 

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

983 except TypeError: 

984 return () 

985 

986 def _restore_tags( 

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

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

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

990 try: 

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

992 return _passthrough 

993 except TypeError: 

994 pass 

995 if type(obj) is dict: 

996 if tags.TUPLE in obj: 

997 restore = self._restore_tuple 

998 elif tags.SET in obj: 

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

1000 elif tags.B64 in obj: 

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

1002 elif tags.B85 in obj: 

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

1004 elif tags.ID in obj: 

1005 restore = self._restore_id 

1006 elif tags.ITERATOR in obj: 

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

1008 elif tags.OBJECT in obj: 

1009 restore = self._restore_object 

1010 elif tags.TYPE in obj: 

1011 restore = self._restore_type 

1012 elif tags.REDUCE in obj: 

1013 restore = self._restore_reduce 

1014 elif tags.FUNCTION in obj: 

1015 restore = self._restore_function 

1016 elif tags.MODULE in obj: 

1017 restore = self._restore_module 

1018 elif tags.REPR in obj: 

1019 if self.safe: 

1020 restore = self._restore_repr_safe 

1021 else: 

1022 restore = self._restore_repr 

1023 else: 

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

1025 elif type(obj) is list: 

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

1027 else: 

1028 restore = _passthrough # type: ignore[assignment] 

1029 return restore