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

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

414 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 decimal 

8import inspect 

9import itertools 

10import sys 

11import types 

12import warnings 

13from itertools import chain, islice 

14from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Type, Union 

15 

16from . import handlers, tags, util 

17from .backend import JSONBackend, json 

18 

19 

20def encode( 

21 value: Any, 

22 unpicklable: bool = True, 

23 make_refs: bool = True, 

24 keys: bool = False, 

25 max_depth: Optional[int] = None, 

26 reset: bool = True, 

27 backend: Optional[JSONBackend] = None, 

28 warn: bool = False, 

29 context: Optional["Pickler"] = None, 

30 max_iter: Optional[int] = None, 

31 use_decimal: bool = False, 

32 numeric_keys: bool = False, 

33 use_base85: bool = False, 

34 fail_safe: Optional[Callable[[Exception], Any]] = None, 

35 indent: Optional[int] = None, 

36 separators: Optional[Any] = None, 

37 include_properties: bool = False, 

38 handle_readonly: bool = False, 

39 handler_context: Any = None, 

40) -> str: 

41 """Return a JSON formatted representation of value, a Python object. 

42 

43 :param unpicklable: If set to ``False`` then the output will not contain the 

44 information necessary to turn the JSON data back into Python objects, 

45 but a simpler JSON stream is produced. It's recommended to set this 

46 parameter to ``False`` when your code does not rely on two objects 

47 having the same ``id()`` value, and when it is sufficient for those two 

48 objects to be equal by ``==``, such as when serializing sklearn 

49 instances. If you experience (de)serialization being incorrect when you 

50 use numpy, pandas, or sklearn handlers, this should be set to ``False``. 

51 If you want the output to not include the dtype for numpy arrays, add:: 

52 

53 jsonpickle.register( 

54 numpy.generic, UnpicklableNumpyGenericHandler, base=True 

55 ) 

56 

57 before your pickling code. 

58 :param make_refs: If set to False jsonpickle's referencing support is 

59 disabled. Objects that are id()-identical won't be preserved across 

60 encode()/decode(), but the resulting JSON stream will be conceptually 

61 simpler. jsonpickle detects cyclical objects and will break the cycle 

62 by calling repr() instead of recursing when make_refs is set False. 

63 :param keys: If set to True then jsonpickle will encode non-string 

64 dictionary keys instead of coercing them into strings via `repr()`. 

65 This is typically what you want if you need to support Integer or 

66 objects as dictionary keys. 

67 :param max_depth: If set to a non-negative integer then jsonpickle will 

68 not recurse deeper than 'max_depth' steps into the object. Anything 

69 deeper than 'max_depth' is represented using a Python repr() of the 

70 object. 

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

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

73 in order to retain object references during pickling. 

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

75 `__getstate__` implementation. 

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

77 jsonpickle will use that backend for deserialization. 

78 :param warn: If set to True then jsonpickle will warn when it 

79 returns None for an object which it cannot pickle 

80 (e.g. file descriptors). 

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

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

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

84 active Pickler and Unpickler objects when custom handlers are 

85 invoked by jsonpickle. 

86 :param max_iter: If set to a non-negative integer then jsonpickle will 

87 consume at most `max_iter` items when pickling iterators. 

88 :param use_decimal: If set to True jsonpickle will allow Decimal 

89 instances to pass-through, with the assumption that the simplejson 

90 backend will be used in `use_decimal` mode. In order to use this mode 

91 you will need to configure simplejson:: 

92 

93 jsonpickle.set_encoder_options('simplejson', 

94 use_decimal=True, sort_keys=True) 

95 jsonpickle.set_decoder_options('simplejson', 

96 use_decimal=True) 

97 jsonpickle.set_preferred_backend('simplejson') 

98 

99 NOTE: A side-effect of the above settings is that float values will be 

100 converted to Decimal when converting to json. 

101 :param numeric_keys: Only use this option if the backend supports integer 

102 dict keys natively. This flag tells jsonpickle to leave numeric keys 

103 as-is rather than conforming them to json-friendly strings. 

104 Using ``keys=True`` is the typical solution for integer keys, so only 

105 use this if you have a specific use case where you want to allow the 

106 backend to handle serialization of numeric dict keys. 

107 :param use_base85: 

108 If possible, use base85 to encode binary data. Base85 bloats binary data 

109 by 1/4 as opposed to base64, which expands it by 1/3. This argument is 

110 ignored on Python 2 because it doesn't support it. 

111 :param fail_safe: If set to a function exceptions are ignored when pickling 

112 and if a exception happens the function is called and the return value 

113 is used as the value for the object that caused the error 

114 :param indent: When `indent` is a non-negative integer, then JSON array 

115 elements and object members will be pretty-printed with that indent 

116 level. An indent level of 0 will only insert newlines. ``None`` is 

117 the most compact representation. Since the default item separator is 

118 ``(', ', ': ')``, the output might include trailing whitespace when 

119 ``indent`` is specified. You can use ``separators=(',', ': ')`` to 

120 avoid this. This value is passed directly to the active JSON backend 

121 library and not used by jsonpickle directly. 

122 :param separators: 

123 If ``separators`` is an ``(item_separator, dict_separator)`` tuple 

124 then it will be used instead of the default ``(', ', ': ')`` 

125 separators. ``(',', ':')`` is the most compact JSON representation. 

126 This value is passed directly to the active JSON backend library and 

127 not used by jsonpickle directly. 

128 :param include_properties: 

129 Include the names and values of class properties in the generated json. 

130 Properties are unpickled properly regardless of this setting, this is 

131 meant to be used if processing the json outside of Python. Certain types 

132 such as sets will not pickle due to not having a native-json equivalent. 

133 Defaults to ``False``. 

134 :param handle_readonly: 

135 Handle objects with readonly methods, such as Django's SafeString. This 

136 basically prevents jsonpickle from raising an exception for such objects. 

137 You MUST set ``handle_readonly=True`` for the decoding if you encode with 

138 this flag set to ``True``. 

139 :param handler_context: 

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

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

142 be found in the examples/ directory on GitHub. 

143 

144 >>> encode('my string') == '"my string"' 

145 True 

146 >>> encode(36) == '36' 

147 True 

148 >>> encode({'foo': True}) == '{"foo": true}' 

149 True 

150 >>> encode({'foo': [1, 2, [3, 4]]}, max_depth=1) 

151 '{"foo": "[1, 2, [3, 4]]"}' 

152 

153 """ 

154 

155 backend = backend or json 

156 context = context or Pickler( 

157 unpicklable=unpicklable, 

158 make_refs=make_refs, 

159 keys=keys, 

160 backend=backend, 

161 max_depth=max_depth, 

162 warn=warn, 

163 max_iter=max_iter, 

164 numeric_keys=numeric_keys, 

165 use_decimal=use_decimal, 

166 use_base85=use_base85, 

167 fail_safe=fail_safe, 

168 include_properties=include_properties, 

169 handle_readonly=handle_readonly, 

170 original_object=value, 

171 handler_context=handler_context, 

172 ) 

173 if handler_context is not None: 

174 context.handler_context = handler_context 

175 return backend.encode( 

176 context.flatten(value, reset=reset), indent=indent, separators=separators 

177 ) 

178 

179 

180def _in_cycle( 

181 obj: Any, objs: Dict[int, int], max_reached: bool, make_refs: bool 

182) -> bool: 

183 """Detect cyclic structures that would lead to infinite recursion""" 

184 return ( 

185 (max_reached or (not make_refs and id(obj) in objs)) 

186 and not util._is_primitive(obj) 

187 and not util._is_enum(obj) 

188 ) 

189 

190 

191def _mktyperef(obj: Type[Any]) -> Dict[str, str]: 

192 """Return a typeref dictionary 

193 

194 >>> _mktyperef(AssertionError) == {'py/type': 'builtins.AssertionError'} 

195 True 

196 

197 """ 

198 return {tags.TYPE: util.importable_name(obj)} 

199 

200 

201def _wrap_string_slot(string: Union[str, Sequence[str]]) -> Sequence[str]: 

202 """Converts __slots__ = 'a' into __slots__ = ('a',)""" 

203 if isinstance(string, str): 

204 return (string,) 

205 return string 

206 

207 

208class Pickler: 

209 def __init__( 

210 self, 

211 unpicklable: bool = True, 

212 make_refs: bool = True, 

213 max_depth: Optional[int] = None, 

214 backend: Optional[JSONBackend] = None, 

215 keys: bool = False, 

216 warn: bool = False, 

217 max_iter: Optional[int] = None, 

218 numeric_keys: bool = False, 

219 use_decimal: bool = False, 

220 use_base85: bool = False, 

221 fail_safe: Optional[Callable[[Exception], Any]] = None, 

222 include_properties: bool = False, 

223 handle_readonly: bool = False, 

224 original_object: Optional[Any] = None, 

225 handler_context: Any = None, 

226 ) -> None: 

227 self.unpicklable = unpicklable 

228 self.make_refs = make_refs 

229 self.backend = backend or json 

230 self.keys = keys 

231 self.warn = warn 

232 self.numeric_keys = numeric_keys 

233 self.use_base85 = use_base85 

234 # The current recursion depth 

235 self._depth = -1 

236 # The maximal recursion depth 

237 self._max_depth = max_depth 

238 # Maps id(obj) to reference IDs 

239 self._objs = {} 

240 # Avoids garbage collection 

241 self._seen = [] 

242 # maximum amount of items to take from a pickled iterator 

243 self._max_iter = max_iter 

244 # Whether to allow decimals to pass-through 

245 self._use_decimal = use_decimal 

246 # A cache of objects that have already been flattened. 

247 self._flattened = {} 

248 # Used for util._is_readonly, see +483 

249 self.handle_readonly = handle_readonly 

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

251 self.handler_context = handler_context 

252 

253 if self.use_base85: 

254 self._bytes_tag = tags.B85 

255 self._bytes_encoder = util.b85encode 

256 else: 

257 self._bytes_tag = tags.B64 

258 self._bytes_encoder = util.b64encode 

259 

260 # ignore exceptions 

261 self.fail_safe = fail_safe 

262 self.include_properties = include_properties 

263 

264 self._original_object = original_object 

265 

266 def _determine_sort_keys(self) -> bool: 

267 for _, options in getattr(self.backend, "_encoder_options", {}).values(): 

268 if options.get("sort_keys", False): 

269 # the user has set one of the backends to sort keys 

270 return True 

271 return False 

272 

273 def _sort_attrs(self, obj: Any) -> Any: 

274 if hasattr(obj, "__slots__") and self.warn: 

275 # Slots are read-only by default, the only way 

276 # to sort keys is to do it in a subclass 

277 # and that would require calling the init function 

278 # of the parent again. That could cause issues 

279 # so we refuse to handle it. 

280 raise TypeError( 

281 "Objects with __slots__ cannot have their keys reliably sorted by " 

282 "jsonpickle! Please sort the keys in the __slots__ definition instead." 

283 ) 

284 # Somehow some classes don't have slots or dict 

285 elif hasattr(obj, "__dict__"): 

286 try: 

287 obj.__dict__ = dict(sorted(obj.__dict__.items())) 

288 except (TypeError, AttributeError): 

289 # Can't set attributes of builtin/extension type 

290 pass 

291 return obj 

292 

293 def reset(self) -> None: 

294 self._objs = {} 

295 self._depth = -1 

296 self._seen = [] 

297 self._flattened = {} 

298 

299 def _push(self) -> None: 

300 """Steps down one level in the namespace.""" 

301 self._depth += 1 

302 

303 def _pop(self, value: Any) -> Any: 

304 """Step up one level in the namespace and return the value. 

305 If we're at the root, reset the pickler's state. 

306 """ 

307 self._depth -= 1 

308 if self._depth == -1: 

309 self.reset() 

310 return value 

311 

312 def _log_ref(self, obj: Any) -> bool: 

313 """ 

314 Log a reference to an in-memory object. 

315 Return True if this object is new and was assigned 

316 a new ID. Otherwise return False. 

317 """ 

318 objid = id(obj) 

319 is_new = objid not in self._objs 

320 if is_new: 

321 new_id = len(self._objs) 

322 self._objs[objid] = new_id 

323 return is_new 

324 

325 def _mkref(self, obj: Any) -> bool: 

326 """ 

327 Log a reference to an in-memory object, and return 

328 if that object should be considered newly logged. 

329 """ 

330 is_new = self._log_ref(obj) 

331 # Pretend the object is new 

332 pretend_new = not self.unpicklable or not self.make_refs 

333 return pretend_new or is_new 

334 

335 def _getref(self, obj: Any) -> Dict[str, int]: 

336 """Return a "py/id" entry for the specified object""" 

337 return {tags.ID: self._objs.get(id(obj))} # type: ignore[dict-item] 

338 

339 def _flatten(self, obj: Any) -> Any: 

340 """Flatten an object and its guts into a json-safe representation""" 

341 if self.unpicklable and self.make_refs: 

342 result = self._flatten_impl(obj) 

343 else: 

344 try: 

345 result = self._flattened[id(obj)] 

346 except KeyError: 

347 result = self._flattened[id(obj)] = self._flatten_impl(obj) 

348 return result 

349 

350 def flatten(self, obj: Any, reset: bool = True) -> Any: 

351 """Takes an object and returns a JSON-safe representation of it. 

352 

353 Simply returns any of the basic builtin datatypes 

354 

355 >>> p = Pickler() 

356 >>> p.flatten('hello world') == 'hello world' 

357 True 

358 >>> p.flatten(49) 

359 49 

360 >>> p.flatten(350.0) 

361 350.0 

362 >>> p.flatten(True) 

363 True 

364 >>> p.flatten(False) 

365 False 

366 >>> r = p.flatten(None) 

367 >>> r is None 

368 True 

369 >>> p.flatten(False) 

370 False 

371 >>> p.flatten([1, 2, 3, 4]) 

372 [1, 2, 3, 4] 

373 >>> p.flatten((1,2,))[tags.TUPLE] 

374 [1, 2] 

375 >>> p.flatten({'key': 'value'}) == {'key': 'value'} 

376 True 

377 """ 

378 if reset: 

379 self.reset() 

380 if self._determine_sort_keys(): 

381 obj = self._sort_attrs(obj) 

382 return self._flatten(obj) 

383 

384 def _flatten_bytestring(self, obj: bytes) -> Dict[str, str]: 

385 return {self._bytes_tag: self._bytes_encoder(obj)} 

386 

387 def _flatten_impl(self, obj: Any) -> Any: 

388 ######################################### 

389 # if obj is nonrecursive return immediately 

390 # for performance reasons we don't want to do recursive checks 

391 if type(obj) is bytes: 

392 return self._flatten_bytestring(obj) 

393 

394 # Decimal is a primitive when use_decimal is True 

395 if type(obj) in (str, bool, int, float, type(None)) or ( 

396 self._use_decimal and isinstance(obj, decimal.Decimal) 

397 ): 

398 return obj 

399 ######################################### 

400 

401 self._push() 

402 return self._pop(self._flatten_obj(obj)) 

403 

404 def _max_reached(self) -> bool: 

405 return self._depth == self._max_depth 

406 

407 def _pickle_warning(self, obj: Any) -> None: 

408 if self.warn: 

409 msg = "jsonpickle cannot pickle %r: replaced with None" % obj 

410 warnings.warn(msg) 

411 

412 def _flatten_obj(self, obj: Any) -> Any: 

413 self._seen.append(obj) 

414 

415 max_reached = self._max_reached() 

416 

417 try: 

418 in_cycle = _in_cycle(obj, self._objs, max_reached, self.make_refs) 

419 if in_cycle: 

420 # break the cycle 

421 flatten_func = repr 

422 else: 

423 flatten_func = self._get_flattener(obj) # type: ignore[assignment] 

424 

425 if flatten_func is None: 

426 self._pickle_warning(obj) 

427 return None 

428 

429 return flatten_func(obj) 

430 

431 except (KeyboardInterrupt, SystemExit) as e: 

432 raise e 

433 except Exception as e: 

434 if self.fail_safe is None: 

435 raise e 

436 else: 

437 return self.fail_safe(e) 

438 

439 def _list_recurse(self, obj: Iterable[Any]) -> List[Any]: 

440 return [self._flatten(v) for v in obj] 

441 

442 def _flatten_function(self, obj: Callable[..., Any]) -> Optional[Dict[str, str]]: 

443 if self.unpicklable: 

444 data = {tags.FUNCTION: util.importable_name(obj)} 

445 else: 

446 data = None 

447 

448 return data 

449 

450 def _getstate(self, obj: Any, data: Dict[str, Any]) -> Dict[str, Any]: 

451 state = self._flatten(obj) 

452 if self.unpicklable: 

453 data[tags.STATE] = state 

454 else: 

455 data = state 

456 return data 

457 

458 def _flatten_key_value_pair( 

459 self, k: Any, v: Any, data: Dict[Union[str, Any], Any] 

460 ) -> Dict[Union[str, Any], Any]: 

461 """Flatten a key/value pair into the passed-in dictionary.""" 

462 if not util._is_picklable(k, v): 

463 return data 

464 # TODO: use inspect.getmembers_static on 3.11+ because it avoids dynamic 

465 # attribute lookups 

466 if ( 

467 self.handle_readonly 

468 and k in {attr for attr, val in inspect.getmembers(self._original_object)} 

469 and util._is_readonly(self._original_object, k, v) 

470 ): 

471 return data 

472 

473 if k is None: 

474 k = "null" # for compatibility with common json encoders 

475 

476 if self.numeric_keys and isinstance(k, (int, float)): 

477 pass 

478 elif not isinstance(k, str): 

479 try: 

480 k = repr(k) 

481 except Exception: 

482 k = str(k) 

483 

484 data[k] = self._flatten(v) 

485 return data 

486 

487 def _call_handler_flatten( 

488 self, handler: handlers.BaseHandler, obj: Any, data: Dict[str, Any] 

489 ) -> Any: 

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

491 if ( 

492 self.handler_context is not None 

493 and handlers.handler_accepts_handler_context(handler.flatten) 

494 ): 

495 kwargs["handler_context"] = self.handler_context 

496 return handler.flatten(obj, data, **kwargs) 

497 

498 def _flatten_obj_attrs( 

499 self, 

500 obj: Any, 

501 attrs: Iterable[str], 

502 data: Dict[str, Any], 

503 exclude: Iterable[str] = (), 

504 ) -> bool: 

505 flatten = self._flatten_key_value_pair 

506 ok = False 

507 exclude = set(exclude) 

508 for k in attrs: 

509 if k in exclude: 

510 continue 

511 try: 

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

513 value = getattr(obj, k) 

514 else: 

515 value = getattr(obj, f"_{obj.__class__.__name__}{k}") 

516 flatten(k, value, data) 

517 except AttributeError: 

518 # The attribute may have been deleted 

519 continue 

520 ok = True 

521 return ok 

522 

523 def _flatten_properties( 

524 self, 

525 obj: Any, 

526 data: Dict[str, Any], 

527 allslots: Optional[Iterable[Sequence[str]]] = None, 

528 ) -> Dict[str, Any]: 

529 if allslots is None: 

530 # setting a list as a default argument can lead to some weird errors 

531 allslots = [] 

532 

533 # convert to set in case there are a lot of slots 

534 allslots_set = set(itertools.chain.from_iterable(allslots)) 

535 

536 # i don't like lambdas 

537 def valid_property(x: tuple[str, Any]) -> bool: 

538 return not x[0].startswith("__") and x[0] not in allslots_set 

539 

540 properties = [ 

541 x[0] for x in inspect.getmembers(obj.__class__) if valid_property(x) 

542 ] 

543 

544 properties_dict = {} 

545 for p_name in properties: 

546 p_val = getattr(obj, p_name) 

547 if util._is_not_class(p_val): 

548 properties_dict[p_name] = p_val 

549 else: 

550 properties_dict[p_name] = self._flatten(p_val) 

551 

552 data[tags.PROPERTY] = properties_dict 

553 

554 return data 

555 

556 def _flatten_newstyle_with_slots( 

557 self, 

558 obj: Any, 

559 data: Dict[str, Any], 

560 exclude: Iterable[str] = (), 

561 ) -> Dict[str, Any]: 

562 """Return a json-friendly dict for new-style objects with __slots__.""" 

563 allslots = [ 

564 _wrap_string_slot(getattr(cls, "__slots__", tuple())) 

565 for cls in obj.__class__.mro() 

566 ] 

567 

568 # add properties to the attribute list 

569 if self.include_properties: 

570 data = self._flatten_properties(obj, data, allslots) 

571 

572 if not self._flatten_obj_attrs(obj, chain(*allslots), data, exclude): 

573 attrs = [ 

574 x for x in dir(obj) if not x.startswith("__") and not x.endswith("__") 

575 ] 

576 self._flatten_obj_attrs(obj, attrs, data, exclude) 

577 

578 return data 

579 

580 def _flatten_obj_instance( 

581 self, obj: Any 

582 ) -> Optional[Union[Dict[str, Any], List[Any], Any]]: 

583 """Recursively flatten an instance and return a json-friendly dict""" 

584 # we're generally not bothering to annotate parts that aren't part of the public API 

585 # but this annotation alone saves us 3 mypy "errors" 

586 data: Dict[str, Any] = {} 

587 has_class = hasattr(obj, "__class__") 

588 has_dict = hasattr(obj, "__dict__") 

589 has_slots = not has_dict and hasattr(obj, "__slots__") 

590 has_getnewargs = util.has_method(obj, "__getnewargs__") 

591 has_getnewargs_ex = util.has_method(obj, "__getnewargs_ex__") 

592 has_getinitargs = util.has_method(obj, "__getinitargs__") 

593 has_reduce, has_reduce_ex = util.has_reduce(obj) 

594 exclude = set(getattr(obj, "_jsonpickle_exclude", ())) 

595 

596 # Support objects with __getstate__(); this ensures that 

597 # both __setstate__() and __getstate__() are implemented 

598 has_own_getstate = hasattr(type(obj), "__getstate__") and type( 

599 obj 

600 ).__getstate__ is not getattr(object, "__getstate__", None) 

601 # not using has_method since __getstate__() is handled separately below 

602 # Note: on Python 3.11+, all objects have __getstate__. 

603 

604 if has_class: 

605 cls = obj.__class__ 

606 else: 

607 cls = type(obj) 

608 

609 # Check for a custom handler 

610 class_name = util.importable_name(cls) 

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

612 if handler is not None: 

613 if self.unpicklable: 

614 data[tags.OBJECT] = class_name 

615 handler_instance = handler(self) 

616 result = self._call_handler_flatten(handler_instance, obj, data) 

617 if result is None: 

618 self._pickle_warning(obj) 

619 return result 

620 

621 reduce_val = None 

622 

623 if self.include_properties: 

624 data = self._flatten_properties(obj, data) 

625 

626 if self.unpicklable: 

627 if has_reduce and not has_reduce_ex: 

628 try: 

629 reduce_val = obj.__reduce__() 

630 except TypeError: 

631 # A lot of builtin types have a reduce which 

632 # just raises a TypeError 

633 # we ignore those 

634 pass 

635 

636 # test for a reduce implementation, and redirect before 

637 # doing anything else if that is what reduce requests 

638 elif has_reduce_ex: 

639 try: 

640 # we're implementing protocol 2 

641 reduce_val = obj.__reduce_ex__(2) 

642 except TypeError: 

643 # A lot of builtin types have a reduce which 

644 # just raises a TypeError 

645 # we ignore those 

646 pass 

647 

648 if reduce_val and isinstance(reduce_val, str): 

649 try: 

650 varpath = iter(reduce_val.split(".")) 

651 # curmod will be transformed by the 

652 # loop into the value to pickle 

653 curmod = sys.modules[next(varpath)] 

654 for modname in varpath: 

655 curmod = getattr(curmod, modname) 

656 # replace obj with value retrieved 

657 return self._flatten(curmod) 

658 except KeyError: 

659 # well, we can't do anything with that, so we ignore it 

660 pass 

661 

662 elif reduce_val: 

663 # at this point, reduce_val should be some kind of iterable 

664 # pad out to len 5 

665 rv_as_list = list(reduce_val) 

666 insufficiency = 5 - len(rv_as_list) 

667 if insufficiency: 

668 rv_as_list += [None] * insufficiency 

669 

670 if getattr(rv_as_list[0], "__name__", "") == "__newobj__": 

671 rv_as_list[0] = tags.NEWOBJ 

672 

673 f, args, state, listitems, dictitems = rv_as_list 

674 

675 # check that getstate/setstate is sane 

676 if not ( 

677 state 

678 and has_own_getstate 

679 and not hasattr(obj, "__setstate__") 

680 and not isinstance(obj, dict) 

681 ): 

682 # turn iterators to iterables for convenient serialization 

683 if rv_as_list[3]: 

684 rv_as_list[3] = tuple(rv_as_list[3]) 

685 

686 if rv_as_list[4]: 

687 rv_as_list[4] = tuple(rv_as_list[4]) 

688 

689 reduce_args = list(map(self._flatten, rv_as_list)) 

690 last_index = len(reduce_args) - 1 

691 while last_index >= 2 and reduce_args[last_index] is None: 

692 last_index -= 1 

693 data[tags.REDUCE] = reduce_args[: last_index + 1] 

694 

695 return data 

696 

697 if has_class and not isinstance(obj, types.ModuleType): 

698 if self.unpicklable: 

699 data[tags.OBJECT] = class_name 

700 

701 if has_getnewargs_ex: 

702 data[tags.NEWARGSEX] = [ 

703 self._flatten(arg) for arg in obj.__getnewargs_ex__() 

704 ] 

705 

706 if has_getnewargs and not has_getnewargs_ex: 

707 data[tags.NEWARGS] = self._flatten(obj.__getnewargs__()) 

708 

709 if has_getinitargs: 

710 data[tags.INITARGS] = self._flatten(obj.__getinitargs__()) 

711 

712 if has_own_getstate: 

713 try: 

714 state = obj.__getstate__() 

715 except TypeError: 

716 # Has getstate but it cannot be called, e.g. file descriptors 

717 # in Python3 

718 self._pickle_warning(obj) 

719 return None 

720 else: 

721 if exclude and isinstance(state, dict): 

722 state = {k: v for k, v in util.items(state, exclude=exclude)} 

723 if state: 

724 return self._getstate(state, data) 

725 

726 if isinstance(obj, types.ModuleType): 

727 if self.unpicklable: 

728 data[tags.MODULE] = "{name}/{name}".format(name=obj.__name__) 

729 else: 

730 # TODO: this causes a mypy assignment error, figure out 

731 # if it's actually an error or a false alarm 

732 data = str(obj) # type: ignore[assignment] 

733 return data 

734 

735 if util._is_dictionary_subclass(obj): 

736 self._flatten_dict_obj(obj, data, exclude=exclude) 

737 return data 

738 

739 if util._is_sequence_subclass(obj): 

740 return self._flatten_sequence_obj(obj, data) 

741 

742 if util._is_iterator(obj): 

743 # force list in python 3 

744 data[tags.ITERATOR] = list(map(self._flatten, islice(obj, self._max_iter))) 

745 return data 

746 

747 if has_dict: 

748 # Support objects that subclasses list and set 

749 if util._is_sequence_subclass(obj): 

750 return self._flatten_sequence_obj(obj, data) 

751 

752 # hack for zope persistent objects; this unghostifies the object 

753 getattr(obj, "_", None) 

754 return self._flatten_dict_obj(obj.__dict__, data, exclude=exclude) 

755 

756 if has_slots: 

757 return self._flatten_newstyle_with_slots(obj, data, exclude=exclude) 

758 

759 # catchall return for data created above without a return 

760 # (e.g. __getnewargs__ is not supposed to be the end of the story) 

761 if data: 

762 return data 

763 

764 self._pickle_warning(obj) 

765 return None 

766 

767 def _ref_obj_instance(self, obj: Any) -> Optional[Union[Dict[str, Any], List[Any]]]: 

768 """Reference an existing object or flatten if new""" 

769 if self.unpicklable: 

770 if self._mkref(obj): 

771 # We've never seen this object so return its 

772 # json representation. 

773 return self._flatten_obj_instance(obj) 

774 # We've seen this object before so place an object 

775 # reference tag in the data. This avoids infinite recursion 

776 # when processing cyclical objects. 

777 return self._getref(obj) 

778 else: 

779 max_reached = self._max_reached() 

780 in_cycle = _in_cycle(obj, self._objs, max_reached, False) 

781 if in_cycle: 

782 # A circular becomes None. 

783 return None 

784 

785 self._mkref(obj) 

786 return self._flatten_obj_instance(obj) 

787 

788 def _escape_key(self, k: Any) -> str: 

789 return tags.JSON_KEY + encode( 

790 k, 

791 reset=False, 

792 keys=True, 

793 context=self, 

794 backend=self.backend, 

795 make_refs=self.make_refs, 

796 ) 

797 

798 def _flatten_non_string_key_value_pair( 

799 self, k: Any, v: Any, data: Dict[str, Any] 

800 ) -> Dict[str, Any]: 

801 """Flatten only non-string key/value pairs""" 

802 if not util._is_picklable(k, v): 

803 return data 

804 if self.keys and not isinstance(k, str): 

805 k = self._escape_key(k) 

806 data[k] = self._flatten(v) 

807 return data 

808 

809 def _flatten_string_key_value_pair( 

810 self, k: str, v: Any, data: Dict[str, Any] 

811 ) -> Dict[str, Any]: 

812 """Flatten string key/value pairs only.""" 

813 if not util._is_picklable(k, v): 

814 return data 

815 if self.keys: 

816 if not isinstance(k, str): 

817 return data 

818 elif k.startswith(tags.JSON_KEY): 

819 k = self._escape_key(k) 

820 else: 

821 if k is None: 

822 k = "null" # for compatibility with common json encoders 

823 

824 if self.numeric_keys and isinstance(k, (int, float)): 

825 pass 

826 elif not isinstance(k, str): 

827 try: 

828 k = repr(k) 

829 except Exception: 

830 k = str(k) 

831 

832 data[k] = self._flatten(v) 

833 return data 

834 

835 def _flatten_dict_obj( 

836 self, 

837 obj: dict[Any, Any], 

838 data: Optional[Dict[Any, Any]] = None, 

839 exclude: Iterable[Any] = (), 

840 ) -> Dict[str, Any]: 

841 """Recursively call flatten() and return json-friendly dict""" 

842 if data is None: 

843 data = obj.__class__() 

844 

845 # If we allow non-string keys then we have to do a two-phase 

846 # encoding to ensure that the reference IDs are deterministic. 

847 if self.keys: 

848 # Phase 1: serialize regular objects, ignore fancy keys. 

849 flatten = self._flatten_string_key_value_pair 

850 for k, v in util.items(obj, exclude=exclude): 

851 flatten(k, v, data) 

852 

853 # Phase 2: serialize non-string keys. 

854 flatten = self._flatten_non_string_key_value_pair 

855 for k, v in util.items(obj, exclude=exclude): 

856 flatten(k, v, data) 

857 else: 

858 # If we have string keys only then we only need a single pass. 

859 flatten = self._flatten_key_value_pair 

860 for k, v in util.items(obj, exclude=exclude): 

861 flatten(k, v, data) 

862 

863 # the collections.defaultdict protocol 

864 if hasattr(obj, "default_factory") and callable(obj.default_factory): 

865 factory = obj.default_factory 

866 # i know that this string could be moved above the hasattr to reduce 

867 # string duplication but mypy 1.18.2 complains and i don't want to use 

868 # even more type: ignores 

869 store_key = "default_factory" 

870 if store_key in data: 

871 store_key = tags.DEFAULT_FACTORY 

872 value: Any 

873 if util._is_type(factory): 

874 # Reference the class/type 

875 # in this case it's Dict[str, str] 

876 value = _mktyperef(factory) 

877 else: 

878 # The factory is not a type and could reference e.g. functions 

879 # or even the object instance itself, which creates a cycle. 

880 if self._mkref(factory): 

881 # We've never seen this object before so pickle it in-place. 

882 # Create an instance from the factory and assume that the 

883 # resulting instance is a suitable exemplar. 

884 value = self._flatten_obj_instance(handlers.CloneFactory(factory())) 

885 else: 

886 # We've seen this object before. 

887 # Break the cycle by emitting a reference. 

888 # in this case it's Dict[str, int] 

889 value = self._getref(factory) 

890 data[store_key] = value 

891 

892 # Sub-classes of dict 

893 if hasattr(obj, "__dict__") and self.unpicklable and obj != obj.__dict__: 

894 if self._mkref(obj.__dict__): 

895 dict_data = {} 

896 self._flatten_dict_obj(obj.__dict__, dict_data, exclude=exclude) 

897 data["__dict__"] = dict_data 

898 else: 

899 data["__dict__"] = self._getref(obj.__dict__) 

900 

901 return data 

902 

903 def _get_flattener(self, obj: Any) -> Optional[Callable[[Any], Any]]: 

904 if type(obj) in (list, dict): 

905 if self._mkref(obj): 

906 return ( 

907 self._list_recurse if type(obj) is list else self._flatten_dict_obj 

908 ) 

909 else: 

910 return self._getref 

911 

912 # We handle tuples and sets by encoding them in a "(tuple|set)dict" 

913 elif type(obj) in (tuple, set): 

914 if not self.unpicklable: 

915 return self._list_recurse 

916 return lambda obj: { 

917 tags.TUPLE if type(obj) is tuple else tags.SET: [ 

918 self._flatten(v) for v in obj 

919 ] 

920 } 

921 

922 elif util._is_module_function(obj): 

923 return self._flatten_function 

924 

925 elif util._is_object(obj): 

926 return self._ref_obj_instance 

927 

928 elif util._is_type(obj): 

929 return _mktyperef 

930 

931 # instance methods, lambdas, old style classes... 

932 self._pickle_warning(obj) 

933 return None 

934 

935 def _flatten_sequence_obj( 

936 self, obj: Iterable[Any], data: Dict[str, Any] 

937 ) -> Union[Dict[str, Any], List[Any]]: 

938 """Return a json-friendly dict for a sequence subclass.""" 

939 if hasattr(obj, "__dict__"): 

940 self._flatten_dict_obj(obj.__dict__, data) 

941 value = [self._flatten(v) for v in obj] 

942 if self.unpicklable: 

943 data[tags.SEQ] = value 

944 else: 

945 return value 

946 return data