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

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

425 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 collections.abc import Callable, Iterable, Sequence 

14from itertools import chain, islice 

15from typing import Any 

16 

17from . import handlers, tags, util 

18from .backend import JSONBackend, json 

19 

20 

21def encode( 

22 value: Any, 

23 unpicklable: bool = True, 

24 make_refs: bool = True, 

25 keys: bool = False, 

26 max_depth: int | None = None, 

27 reset: bool = True, 

28 backend: JSONBackend | None = None, 

29 warn: bool = False, 

30 context: "Pickler | None" = None, 

31 max_iter: int | None = None, 

32 use_decimal: bool = False, 

33 numeric_keys: bool = False, 

34 use_base85: bool = False, 

35 fail_safe: Callable[[Exception], Any] | None = None, 

36 indent: int | None = None, 

37 separators: Any | None = None, 

38 include_properties: bool = False, 

39 handle_readonly: bool = False, 

40 handler_context: Any = None, 

41) -> str: 

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

43 

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

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

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

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

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

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

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

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

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

53 

54 jsonpickle.register( 

55 numpy.generic, UnpicklableNumpyGenericHandler, base=True 

56 ) 

57 

58 before your pickling code. 

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

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

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

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

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

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

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

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

67 objects as dictionary keys. 

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

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

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

71 object. 

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

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

74 in order to retain object references during pickling. 

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

76 `__getstate__` implementation. 

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

78 jsonpickle will use that backend for serialization. 

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

80 returns None for an object which it cannot pickle 

81 (e.g. file descriptors). 

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

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

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

85 active Pickler and Unpickler objects when custom handlers are 

86 invoked by jsonpickle. 

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

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

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

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

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

92 you will need to configure simplejson:: 

93 

94 jsonpickle.set_encoder_options('simplejson', 

95 use_decimal=True, sort_keys=True) 

96 jsonpickle.set_decoder_options('simplejson', 

97 use_decimal=True) 

98 jsonpickle.set_preferred_backend('simplejson') 

99 

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

101 converted to Decimal when converting to json. 

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

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

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

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

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

107 backend to handle serialization of numeric dict keys. 

108 :param use_base85: 

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

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

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

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

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

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

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

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

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

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

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

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

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

122 library and not used by jsonpickle directly. 

123 :param separators: 

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

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

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

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

128 not used by jsonpickle directly. 

129 :param include_properties: 

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

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

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

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

134 Defaults to ``False``. 

135 :param handle_readonly: 

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

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

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

139 this flag set to ``True``. 

140 :param handler_context: 

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

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

143 be found in the examples/ directory on GitHub. 

144 

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

146 True 

147 >>> encode(36) == '36' 

148 True 

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

150 True 

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

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

153 

154 """ 

155 

156 backend = backend or json 

157 context = context or Pickler( 

158 unpicklable=unpicklable, 

159 make_refs=make_refs, 

160 keys=keys, 

161 backend=backend, 

162 max_depth=max_depth, 

163 warn=warn, 

164 max_iter=max_iter, 

165 numeric_keys=numeric_keys, 

166 use_decimal=use_decimal, 

167 use_base85=use_base85, 

168 fail_safe=fail_safe, 

169 include_properties=include_properties, 

170 handle_readonly=handle_readonly, 

171 original_object=value, 

172 handler_context=handler_context, 

173 ) 

174 if handler_context is not None: 

175 context.handler_context = handler_context 

176 return backend.encode( 

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

178 ) 

179 

180 

181def _in_cycle( 

182 obj: Any, objs: dict[int, int], max_reached: bool, make_refs: bool 

183) -> bool: 

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

185 return ( 

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

187 and not util._is_primitive(obj) 

188 and not util._is_enum(obj) 

189 ) 

190 

191 

192def _mktyperef(obj: type) -> dict[str, str]: 

193 """Return a typeref dictionary 

194 

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

196 True 

197 

198 """ 

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

200 

201 

202def _wrap_string_slot(string: str | Sequence[str]) -> Sequence[str]: 

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

204 if isinstance(string, str): 

205 return (string,) 

206 return string 

207 

208 

209class Pickler: 

210 def __init__( 

211 self, 

212 unpicklable: bool = True, 

213 make_refs: bool = True, 

214 max_depth: int | None = None, 

215 backend: JSONBackend | None = None, 

216 keys: bool = False, 

217 warn: bool = False, 

218 max_iter: int | None = None, 

219 numeric_keys: bool = False, 

220 use_decimal: bool = False, 

221 use_base85: bool = False, 

222 fail_safe: Callable[[Exception], Any] | None = None, 

223 include_properties: bool = False, 

224 handle_readonly: bool = False, 

225 original_object: Any | None = None, 

226 handler_context: Any = None, 

227 ) -> None: 

228 self.unpicklable = unpicklable 

229 self.make_refs = make_refs 

230 self.backend = backend or json 

231 self.keys = keys 

232 self.warn = warn 

233 self.numeric_keys = numeric_keys 

234 self.use_base85 = use_base85 

235 # The current recursion depth 

236 self._depth = -1 

237 # The maximal recursion depth 

238 self._max_depth = max_depth 

239 # Maps id(obj) to reference IDs 

240 self._objs = {} 

241 # Avoids garbage collection 

242 self._seen = [] 

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

244 self._max_iter = max_iter 

245 # Whether to allow decimals to pass-through 

246 self._use_decimal = use_decimal 

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

248 self._flattened = {} 

249 # Used for util._is_readonly, see +483 

250 self.handle_readonly = handle_readonly 

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

252 self.handler_context = handler_context 

253 

254 if self.use_base85: 

255 self._bytes_tag = tags.B85 

256 self._bytes_encoder = util.b85encode 

257 else: 

258 self._bytes_tag = tags.B64 

259 self._bytes_encoder = util.b64encode 

260 

261 # ignore exceptions 

262 self.fail_safe = fail_safe 

263 self.include_properties = include_properties 

264 

265 self._original_object = original_object 

266 

267 def _determine_sort_keys(self) -> bool: 

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

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

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

271 return True 

272 return False 

273 

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

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

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

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

278 # and that would require calling the init function 

279 # of the parent again. That could cause issues 

280 # so we refuse to handle it. 

281 raise TypeError( 

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

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

284 ) 

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

286 elif hasattr(obj, "__dict__"): 

287 try: 

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

289 except (TypeError, AttributeError): 

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

291 pass 

292 return obj 

293 

294 def reset(self) -> None: 

295 self._objs = {} 

296 self._depth = -1 

297 self._seen = [] 

298 self._flattened = {} 

299 

300 def _push(self) -> None: 

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

302 self._depth += 1 

303 

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

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

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

307 """ 

308 self._depth -= 1 

309 if self._depth == -1: 

310 self.reset() 

311 return value 

312 

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

314 """ 

315 Log a reference to an in-memory object. 

316 Return True if this object is new and was assigned 

317 a new ID. Otherwise return False. 

318 """ 

319 objid = id(obj) 

320 is_new = objid not in self._objs 

321 if is_new: 

322 new_id = len(self._objs) 

323 self._objs[objid] = new_id 

324 return is_new 

325 

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

327 """ 

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

329 if that object should be considered newly logged. 

330 """ 

331 is_new = self._log_ref(obj) 

332 # Pretend the object is new 

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

334 return pretend_new or is_new 

335 

336 def _getref(self, obj: Any) -> dict[str, int]: 

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

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

339 

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

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

342 if self.unpicklable and self.make_refs: 

343 result = self._flatten_impl(obj) 

344 else: 

345 try: 

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

347 except KeyError: 

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

349 return result 

350 

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

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

353 

354 Simply returns any of the basic builtin datatypes 

355 

356 >>> p = Pickler() 

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

358 True 

359 >>> p.flatten(49) 

360 49 

361 >>> p.flatten(350.0) 

362 350.0 

363 >>> p.flatten(True) 

364 True 

365 >>> p.flatten(False) 

366 False 

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

368 >>> r is None 

369 True 

370 >>> p.flatten(False) 

371 False 

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

373 [1, 2, 3, 4] 

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

375 [1, 2] 

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

377 True 

378 """ 

379 if reset: 

380 self.reset() 

381 if self._determine_sort_keys(): 

382 obj = self._sort_attrs(obj) 

383 return self._flatten(obj) 

384 

385 def _flatten_bytestring(self, obj: bytes) -> dict[str, str]: 

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

387 

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

389 ######################################### 

390 # if obj is nonrecursive return immediately 

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

392 if type(obj) is bytes: 

393 return self._flatten_bytestring(obj) 

394 

395 # Decimal is a primitive when use_decimal is True 

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

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

398 ): 

399 return obj 

400 ######################################### 

401 

402 self._push() 

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

404 

405 def _max_reached(self) -> bool: 

406 return self._depth == self._max_depth 

407 

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

409 if self.warn: 

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

411 warnings.warn(msg) 

412 

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

414 self._seen.append(obj) 

415 

416 max_reached = self._max_reached() 

417 

418 try: 

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

420 flatten_func: Callable[[Any], str] | None 

421 if in_cycle: 

422 # break the cycle 

423 flatten_func = repr 

424 else: 

425 flatten_func = self._get_flattener(obj) 

426 

427 if flatten_func is None: 

428 self._pickle_warning(obj) 

429 return None 

430 

431 return flatten_func(obj) 

432 

433 except (KeyboardInterrupt, SystemExit) as e: 

434 raise e 

435 except Exception as e: 

436 if self.fail_safe is None: 

437 raise e 

438 else: 

439 return self.fail_safe(e) 

440 

441 def _list_recurse(self, obj: Iterable[Any]) -> list[Any]: 

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

443 

444 def _flatten_function(self, obj: Callable[..., Any]) -> dict[str, str] | None: 

445 if self.unpicklable: 

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

447 else: 

448 data = None 

449 

450 return data 

451 

452 def _getstate(self, obj: Any, data: dict[str, Any]) -> dict[str, Any]: 

453 state = self._flatten(obj) 

454 if self.unpicklable: 

455 data[tags.STATE] = state 

456 else: 

457 data = state 

458 return data 

459 

460 def _flatten_key_value_pair( 

461 self, k: Any, v: Any, data: dict[str | Any, Any] 

462 ) -> dict[str | Any, Any]: 

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

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

465 return data 

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

467 # attribute lookups 

468 if ( 

469 self.handle_readonly 

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

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

472 ): 

473 return data 

474 

475 if k is None: 

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

477 

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

479 pass 

480 elif not isinstance(k, str): 

481 try: 

482 k = repr(k) 

483 except Exception: 

484 k = str(k) 

485 

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

487 return data 

488 

489 def _call_handler_flatten( 

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

491 ) -> Any: 

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

493 if ( 

494 self.handler_context is not None 

495 and handlers.handler_accepts_handler_context(handler.flatten) 

496 ): 

497 kwargs["handler_context"] = self.handler_context 

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

499 

500 def _flatten_obj_attrs( 

501 self, 

502 obj: Any, 

503 attrs: Iterable[str], 

504 data: dict[str, Any], 

505 exclude: Iterable[str] = (), 

506 ) -> bool: 

507 flatten = self._flatten_key_value_pair 

508 ok = False 

509 exclude = set(exclude) 

510 for k in attrs: 

511 if k in exclude: 

512 continue 

513 try: 

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

515 value = getattr(obj, k) 

516 else: 

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

518 flatten(k, value, data) 

519 except AttributeError: 

520 # The attribute may have been deleted 

521 continue 

522 ok = True 

523 return ok 

524 

525 def _flatten_properties( 

526 self, 

527 obj: Any, 

528 data: dict[str, Any], 

529 allslots: Iterable[Sequence[str]] | None = None, 

530 ) -> dict[str, Any]: 

531 if allslots is None: 

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

533 allslots = [] 

534 

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

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

537 

538 # i don't like lambdas 

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

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

541 

542 properties = [ 

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

544 ] 

545 

546 properties_dict = {} 

547 for p_name in properties: 

548 p_val = getattr(obj, p_name) 

549 if util._is_not_class(p_val): 

550 properties_dict[p_name] = p_val 

551 else: 

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

553 

554 data[tags.PROPERTY] = properties_dict 

555 

556 return data 

557 

558 def _flatten_newstyle_with_slots( 

559 self, 

560 obj: Any, 

561 data: dict[str, Any], 

562 exclude: Iterable[str] = (), 

563 ) -> dict[str, Any]: 

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

565 allslots = [ 

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

567 for cls in obj.__class__.mro() 

568 ] 

569 

570 # add properties to the attribute list 

571 if self.include_properties: 

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

573 

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

575 attrs = [ 

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

577 ] 

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

579 

580 return data 

581 

582 def _reduce(self, obj: Any, has_reduce: bool, has_reduce_ex: bool) -> Any: 

583 """Return the object's __reduce__/__reduce_ex__ output, or None. 

584 

585 Many builtin types raise TypeError from these; treat that as 

586 "no reduce available" rather than letting it propagate. 

587 """ 

588 try: 

589 if has_reduce and not has_reduce_ex: 

590 return obj.__reduce__() 

591 if has_reduce_ex: 

592 # we're implementing protocol 2 

593 return obj.__reduce_ex__(2) 

594 except TypeError: 

595 pass 

596 return None 

597 

598 def _flatten_obj_instance( 

599 self, obj: Any 

600 ) -> dict[str, Any] | list[Any] | Any | None: 

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

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

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

604 data: dict[str, Any] = {} 

605 has_class = hasattr(obj, "__class__") 

606 has_dict = hasattr(obj, "__dict__") 

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

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

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

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

611 has_reduce, has_reduce_ex = util.has_reduce(obj) 

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

613 

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

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

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

617 obj 

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

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

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

621 

622 if has_class: 

623 cls = obj.__class__ 

624 else: 

625 cls = type(obj) 

626 

627 # Check for a custom handler 

628 class_name = util.importable_name(cls) 

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

630 if handler is not None: 

631 if self.unpicklable: 

632 data[tags.OBJECT] = class_name 

633 handler_instance = handler(self) 

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

635 if result is None: 

636 self._pickle_warning(obj) 

637 return result 

638 

639 if self.include_properties: 

640 data = self._flatten_properties(obj, data) 

641 

642 if self.unpicklable: 

643 # test for a reduce implementation, and redirect before 

644 # doing anything else if that is what reduce requests 

645 reduce_val = self._reduce(obj, has_reduce, has_reduce_ex) 

646 

647 if reduce_val and isinstance(reduce_val, str): 

648 try: 

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

650 # curmod will be transformed by the 

651 # loop into the value to pickle 

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

653 for modname in varpath: 

654 curmod = getattr(curmod, modname) 

655 # replace obj with value retrieved 

656 return self._flatten(curmod) 

657 except KeyError: 

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

659 pass 

660 

661 elif reduce_val: 

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

663 # pad out to len 5 

664 rv_as_list = list(reduce_val) 

665 insufficiency = 5 - len(rv_as_list) 

666 if insufficiency: 

667 rv_as_list += [None] * insufficiency 

668 

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

670 rv_as_list[0] = tags.NEWOBJ 

671 

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

673 

674 # check that getstate/setstate is sane 

675 if not ( 

676 state 

677 and has_own_getstate 

678 and not hasattr(obj, "__setstate__") 

679 and not isinstance(obj, dict) 

680 ): 

681 # turn iterators to iterables for convenient serialization 

682 if rv_as_list[3]: 

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

684 

685 if rv_as_list[4]: 

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

687 

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

689 last_index = len(reduce_args) - 1 

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

691 last_index -= 1 

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

693 

694 return data 

695 

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

697 if self.unpicklable: 

698 data[tags.OBJECT] = class_name 

699 

700 if has_getnewargs_ex: 

701 data[tags.NEWARGSEX] = [ 

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

703 ] 

704 

705 if has_getnewargs and not has_getnewargs_ex: 

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

707 

708 if has_getinitargs: 

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

710 

711 if has_own_getstate: 

712 try: 

713 state = obj.__getstate__() 

714 except TypeError: 

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

716 # in Python3 

717 self._pickle_warning(obj) 

718 return None 

719 else: 

720 if exclude and isinstance(state, dict): 

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

722 if state: 

723 return self._getstate(state, data) 

724 

725 if isinstance(obj, types.ModuleType): 

726 if self.unpicklable: 

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

728 else: 

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

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

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

732 return data 

733 

734 if util._is_dictionary_subclass(obj): 

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

736 return data 

737 

738 if util._is_sequence_subclass(obj): 

739 return self._flatten_sequence_obj(obj, data) 

740 

741 if util._is_iterator(obj): 

742 # force list in python 3 

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

744 return data 

745 

746 if has_dict: 

747 # Support objects that subclasses list and set 

748 if util._is_sequence_subclass(obj): 

749 return self._flatten_sequence_obj(obj, data) 

750 

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

752 getattr(obj, "_", None) 

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

754 

755 if has_slots: 

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

757 

758 # catchall return for data created above without a return 

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

760 if data: 

761 return data 

762 

763 # Objects whose state is only reachable through __reduce__/__reduce_ex__ 

764 # (e.g. datetime.timedelta) have no __dict__, __slots__ or __getstate__ 

765 # for the branches above to read, so nothing has been produced and they 

766 # would otherwise become null. Emit a lossy view built from the reduce 

767 # output instead: its state if present, else the constructor args. The 

768 # string form and the listitems/dictitems slots (append/update-based 

769 # reconstruction) are not represented and keep the previous behaviour. 

770 if not self.unpicklable: 

771 reduce_val = self._reduce(obj, has_reduce, has_reduce_ex) 

772 if reduce_val is not None and not isinstance(reduce_val, str): 

773 # reduce tuple: (callable, args, state, listitems, dictitems) 

774 rv_as_list = list(reduce_val) 

775 state = rv_as_list[2] if len(rv_as_list) > 2 else None 

776 if state: 

777 return self._flatten(state) 

778 args = rv_as_list[1] if len(rv_as_list) > 1 else None 

779 if args: 

780 return self._flatten(args) 

781 

782 self._pickle_warning(obj) 

783 return None 

784 

785 def _ref_obj_instance(self, obj: Any) -> dict[str, Any] | list[Any] | None: 

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

787 if self.unpicklable: 

788 if self._mkref(obj): 

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

790 # json representation. 

791 return self._flatten_obj_instance(obj) 

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

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

794 # when processing cyclical objects. 

795 return self._getref(obj) 

796 else: 

797 max_reached = self._max_reached() 

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

799 if in_cycle: 

800 # A circular becomes None. 

801 return None 

802 

803 self._mkref(obj) 

804 return self._flatten_obj_instance(obj) 

805 

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

807 return tags.JSON_KEY + encode( 

808 k, 

809 reset=False, 

810 keys=True, 

811 context=self, 

812 backend=self.backend, 

813 make_refs=self.make_refs, 

814 ) 

815 

816 def _flatten_non_string_key_value_pair( 

817 self, k: Any, v: Any, data: dict[str, Any] 

818 ) -> dict[str, Any]: 

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

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

821 return data 

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

823 k = self._escape_key(k) 

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

825 return data 

826 

827 def _flatten_string_key_value_pair( 

828 self, k: str, v: Any, data: dict[str, Any] 

829 ) -> dict[str, Any]: 

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

831 if ( 

832 isinstance(k, str) 

833 and (k.startswith(tags.JSON_KEY) or k in tags.RESERVED) 

834 and self.keys 

835 ): 

836 # Escape data keys colliding with the json:// prefix or a reserved 

837 # wire tag; must run before _is_picklable, which drops RESERVED keys. 

838 data[self._escape_key(k)] = self._flatten(v) 

839 return data 

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

841 return data 

842 if self.keys: 

843 if not isinstance(k, str): 

844 return data 

845 else: 

846 if k is None: 

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

848 

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

850 pass 

851 elif not isinstance(k, str): 

852 try: 

853 k = repr(k) 

854 except Exception: 

855 k = str(k) 

856 

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

858 return data 

859 

860 def _flatten_dict_obj( 

861 self, 

862 obj: dict[Any, Any], 

863 data: dict[Any, Any] | None = None, 

864 exclude: Iterable[Any] = (), 

865 ) -> dict[str, Any]: 

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

867 if data is None: 

868 data = obj.__class__() 

869 

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

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

872 if self.keys: 

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

874 flatten = self._flatten_string_key_value_pair 

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

876 flatten(k, v, data) 

877 

878 # Phase 2: serialize non-string keys. 

879 flatten = self._flatten_non_string_key_value_pair 

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

881 flatten(k, v, data) 

882 else: 

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

884 flatten = self._flatten_key_value_pair 

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

886 flatten(k, v, data) 

887 

888 # the collections.defaultdict protocol 

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

890 factory = obj.default_factory 

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

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

893 # even more type: ignores 

894 store_key = "default_factory" 

895 if store_key in data: 

896 store_key = tags.DEFAULT_FACTORY 

897 value: Any 

898 if util._is_type(factory): 

899 # Reference the class/type 

900 # in this case it's dict[str, str] 

901 value = _mktyperef(factory) 

902 else: 

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

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

905 if self._mkref(factory): 

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

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

908 # resulting instance is a suitable exemplar. 

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

910 else: 

911 # We've seen this object before. 

912 # Break the cycle by emitting a reference. 

913 # in this case it's dict[str, int] 

914 value = self._getref(factory) 

915 data[store_key] = value 

916 

917 # Sub-classes of dict 

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

919 if self._mkref(obj.__dict__): 

920 dict_data = {} 

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

922 data["__dict__"] = dict_data 

923 else: 

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

925 

926 return data 

927 

928 def _get_flattener(self, obj: Any) -> Callable[[Any], Any] | None: 

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

930 if self._mkref(obj): 

931 return ( 

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

933 ) 

934 else: 

935 return self._getref 

936 

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

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

939 if not self.unpicklable: 

940 return self._list_recurse 

941 return lambda obj: { 

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

943 self._flatten(v) for v in obj 

944 ] 

945 } 

946 

947 elif util._is_module_function(obj): 

948 return self._flatten_function 

949 

950 elif util._is_object(obj): 

951 return self._ref_obj_instance 

952 

953 elif util._is_type(obj): 

954 return _mktyperef 

955 

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

957 self._pickle_warning(obj) 

958 return None 

959 

960 def _flatten_sequence_obj( 

961 self, obj: Iterable[Any], data: dict[str, Any] 

962 ) -> dict[str, Any] | list[Any]: 

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

964 if hasattr(obj, "__dict__"): 

965 self._flatten_dict_obj(obj.__dict__, data) 

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

967 if self.unpicklable: 

968 data[tags.SEQ] = value 

969 else: 

970 return value 

971 return data