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

397 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 ( 

15 Any, 

16 Callable, 

17 Dict, 

18 Iterable, 

19 List, 

20 Optional, 

21 Sequence, 

22 Type, 

23 Union, 

24) 

25 

26from . import handlers, tags, util 

27from .backend import JSONBackend, json 

28 

29 

30def encode( 

31 value: Any, 

32 unpicklable: bool = True, 

33 make_refs: bool = True, 

34 keys: bool = False, 

35 max_depth: Optional[int] = None, 

36 reset: bool = True, 

37 backend: Optional[JSONBackend] = None, 

38 warn: bool = False, 

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

40 max_iter: Optional[int] = None, 

41 use_decimal: bool = False, 

42 numeric_keys: bool = False, 

43 use_base85: bool = False, 

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

45 indent: Optional[int] = None, 

46 separators: Optional[Any] = None, 

47 include_properties: bool = False, 

48 handle_readonly: bool = False, 

49) -> str: 

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

51 

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

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

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

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

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

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

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

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

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

61 

62 jsonpickle.register( 

63 numpy.generic, UnpicklableNumpyGenericHandler, base=True 

64 ) 

65 

66 before your pickling code. 

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

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

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

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

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

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

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

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

75 objects as dictionary keys. 

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

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

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

79 object. 

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

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

82 in order to retain object references during pickling. 

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

84 `__getstate__` implementation. 

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

86 jsonpickle will use that backend for deserialization. 

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

88 returns None for an object which it cannot pickle 

89 (e.g. file descriptors). 

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

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

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

93 active Pickler and Unpickler objects when custom handlers are 

94 invoked by jsonpickle. 

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

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

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

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

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

100 you will need to configure simplejson:: 

101 

102 jsonpickle.set_encoder_options('simplejson', 

103 use_decimal=True, sort_keys=True) 

104 jsonpickle.set_decoder_options('simplejson', 

105 use_decimal=True) 

106 jsonpickle.set_preferred_backend('simplejson') 

107 

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

109 converted to Decimal when converting to json. 

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

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

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

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

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

115 backend to handle serialization of numeric dict keys. 

116 :param use_base85: 

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

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

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

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

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

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

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

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

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

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

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

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

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

130 library and not used by jsonpickle directly. 

131 :param separators: 

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

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

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

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

136 not used by jsonpickle directly. 

137 :param include_properties: 

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

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

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

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

142 Defaults to ``False``. 

143 :param handle_readonly: 

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

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

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

147 this flag set to ``True``. 

148 

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

150 True 

151 >>> encode(36) == '36' 

152 True 

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

154 True 

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

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

157 

158 """ 

159 backend = backend or json 

160 context = context or Pickler( 

161 unpicklable=unpicklable, 

162 make_refs=make_refs, 

163 keys=keys, 

164 backend=backend, 

165 max_depth=max_depth, 

166 warn=warn, 

167 max_iter=max_iter, 

168 numeric_keys=numeric_keys, 

169 use_decimal=use_decimal, 

170 use_base85=use_base85, 

171 fail_safe=fail_safe, 

172 include_properties=include_properties, 

173 handle_readonly=handle_readonly, 

174 original_object=value, 

175 ) 

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[Any]) -> 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: Union[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: Optional[int] = None, 

215 backend: Optional[JSONBackend] = None, 

216 keys: bool = False, 

217 warn: bool = False, 

218 max_iter: Optional[int] = None, 

219 numeric_keys: bool = False, 

220 use_decimal: bool = False, 

221 use_base85: bool = False, 

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

223 include_properties: bool = False, 

224 handle_readonly: bool = False, 

225 original_object: Optional[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 

251 if self.use_base85: 

252 self._bytes_tag = tags.B85 

253 self._bytes_encoder = util.b85encode 

254 else: 

255 self._bytes_tag = tags.B64 

256 self._bytes_encoder = util.b64encode 

257 

258 # ignore exceptions 

259 self.fail_safe = fail_safe 

260 self.include_properties = include_properties 

261 

262 self._original_object = original_object 

263 

264 def _determine_sort_keys(self) -> bool: 

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

266 if options.get('sort_keys', False): 

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

268 return True 

269 return False 

270 

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

272 if hasattr(obj, '__slots__') and self.warn: 

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

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

275 # and that would require calling the init function 

276 # of the parent again. That could cause issues 

277 # so we refuse to handle it. 

278 raise TypeError( 

279 'Objects with __slots__ cannot have their keys reliably sorted by ' 

280 'jsonpickle! Please sort the keys in the __slots__ definition instead.' 

281 ) 

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

283 elif hasattr(obj, '__dict__'): 

284 try: 

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

286 except (TypeError, AttributeError): 

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

288 pass 

289 return obj 

290 

291 def reset(self) -> None: 

292 self._objs = {} 

293 self._depth = -1 

294 self._seen = [] 

295 self._flattened = {} 

296 

297 def _push(self) -> None: 

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

299 self._depth += 1 

300 

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

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

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

304 """ 

305 self._depth -= 1 

306 if self._depth == -1: 

307 self.reset() 

308 return value 

309 

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

311 """ 

312 Log a reference to an in-memory object. 

313 Return True if this object is new and was assigned 

314 a new ID. Otherwise return False. 

315 """ 

316 objid = id(obj) 

317 is_new = objid not in self._objs 

318 if is_new: 

319 new_id = len(self._objs) 

320 self._objs[objid] = new_id 

321 return is_new 

322 

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

324 """ 

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

326 if that object should be considered newly logged. 

327 """ 

328 is_new = self._log_ref(obj) 

329 # Pretend the object is new 

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

331 return pretend_new or is_new 

332 

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

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

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

336 

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

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

339 if self.unpicklable and self.make_refs: 

340 result = self._flatten_impl(obj) 

341 else: 

342 try: 

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

344 except KeyError: 

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

346 return result 

347 

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

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

350 

351 Simply returns any of the basic builtin datatypes 

352 

353 >>> p = Pickler() 

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

355 True 

356 >>> p.flatten(49) 

357 49 

358 >>> p.flatten(350.0) 

359 350.0 

360 >>> p.flatten(True) 

361 True 

362 >>> p.flatten(False) 

363 False 

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

365 >>> r is None 

366 True 

367 >>> p.flatten(False) 

368 False 

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

370 [1, 2, 3, 4] 

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

372 [1, 2] 

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

374 True 

375 """ 

376 if reset: 

377 self.reset() 

378 if self._determine_sort_keys(): 

379 obj = self._sort_attrs(obj) 

380 return self._flatten(obj) 

381 

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

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

384 

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

386 ######################################### 

387 # if obj is nonrecursive return immediately 

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

389 if type(obj) is bytes: 

390 return self._flatten_bytestring(obj) 

391 

392 # Decimal is a primitive when use_decimal is True 

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

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

395 ): 

396 return obj 

397 ######################################### 

398 

399 self._push() 

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

401 

402 def _max_reached(self) -> bool: 

403 return self._depth == self._max_depth 

404 

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

406 if self.warn: 

407 msg = 'jsonpickle cannot pickle %r: replaced with None' % obj 

408 warnings.warn(msg) 

409 

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

411 self._seen.append(obj) 

412 

413 max_reached = self._max_reached() 

414 

415 try: 

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

417 if in_cycle: 

418 # break the cycle 

419 flatten_func = repr 

420 else: 

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

422 

423 if flatten_func is None: 

424 self._pickle_warning(obj) 

425 return None 

426 

427 return flatten_func(obj) 

428 

429 except (KeyboardInterrupt, SystemExit) as e: 

430 raise e 

431 except Exception as e: 

432 if self.fail_safe is None: 

433 raise e 

434 else: 

435 return self.fail_safe(e) 

436 

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

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

439 

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

441 if self.unpicklable: 

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

443 else: 

444 data = None 

445 

446 return data 

447 

448 def _getstate(self, obj: Any, data: Dict[str, Any]): 

449 state = self._flatten(obj) 

450 if self.unpicklable: 

451 data[tags.STATE] = state 

452 else: 

453 data = state 

454 return data 

455 

456 def _flatten_key_value_pair( 

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

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

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

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

461 return data 

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

463 # attribute lookups 

464 if ( 

465 self.handle_readonly 

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

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

468 ): 

469 return data 

470 

471 if k is None: 

472 k = 'null' # for compatibility with common json encoders 

473 

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

475 pass 

476 elif not isinstance(k, str): 

477 try: 

478 k = repr(k) 

479 except Exception: 

480 k = str(k) 

481 

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

483 return data 

484 

485 def _flatten_obj_attrs( 

486 self, obj: Any, attrs: Iterable[str], data: Dict[str, Any] 

487 ) -> bool: 

488 flatten = self._flatten_key_value_pair 

489 ok = False 

490 for k in attrs: 

491 try: 

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

493 value = getattr(obj, k) 

494 else: 

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

496 flatten(k, value, data) 

497 except AttributeError: 

498 # The attribute may have been deleted 

499 continue 

500 ok = True 

501 return ok 

502 

503 def _flatten_properties( 

504 self, 

505 obj: Any, 

506 data: Dict[str, Any], 

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

508 ) -> Dict[str, Any]: 

509 if allslots is None: 

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

511 allslots = [] 

512 

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

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

515 

516 # i don't like lambdas 

517 def valid_property(x): 

518 return not x[0].startswith('__') and x[0] not in allslots_set 

519 

520 properties = [ 

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

522 ] 

523 

524 properties_dict = {} 

525 for p_name in properties: 

526 p_val = getattr(obj, p_name) 

527 if util._is_not_class(p_val): 

528 properties_dict[p_name] = p_val 

529 else: 

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

531 

532 data[tags.PROPERTY] = properties_dict 

533 

534 return data 

535 

536 def _flatten_newstyle_with_slots( 

537 self, obj: Any, data: Dict[str, Any] 

538 ) -> Dict[str, Any]: 

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

540 allslots = [ 

541 _wrap_string_slot(getattr(cls, '__slots__', tuple())) 

542 for cls in obj.__class__.mro() 

543 ] 

544 

545 # add properties to the attribute list 

546 if self.include_properties: 

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

548 

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

550 attrs = [ 

551 x for x in dir(obj) if not x.startswith('__') and not x.endswith('__') 

552 ] 

553 self._flatten_obj_attrs(obj, attrs, data) 

554 

555 return data 

556 

557 def _flatten_obj_instance( 

558 self, obj: Any 

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

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

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

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

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

564 has_class = hasattr(obj, '__class__') 

565 has_dict = hasattr(obj, '__dict__') 

566 has_slots = not has_dict and hasattr(obj, '__slots__') 

567 has_getnewargs = util.has_method(obj, '__getnewargs__') 

568 has_getnewargs_ex = util.has_method(obj, '__getnewargs_ex__') 

569 has_getinitargs = util.has_method(obj, '__getinitargs__') 

570 has_reduce, has_reduce_ex = util.has_reduce(obj) 

571 exclude = set(getattr(obj, '_jsonpickle_exclude', ())) 

572 

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

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

575 has_own_getstate = hasattr(type(obj), '__getstate__') and type( 

576 obj 

577 ).__getstate__ is not getattr(object, '__getstate__', None) 

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

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

580 

581 if has_class: 

582 cls = obj.__class__ 

583 else: 

584 cls = type(obj) 

585 

586 # Check for a custom handler 

587 class_name = util.importable_name(cls) 

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

589 if handler is not None: 

590 if self.unpicklable: 

591 data[tags.OBJECT] = class_name 

592 result = handler(self).flatten(obj, data) 

593 if result is None: 

594 self._pickle_warning(obj) 

595 return result 

596 

597 reduce_val = None 

598 

599 if self.include_properties: 

600 data = self._flatten_properties(obj, data) 

601 

602 if self.unpicklable: 

603 if has_reduce and not has_reduce_ex: 

604 try: 

605 reduce_val = obj.__reduce__() 

606 except TypeError: 

607 # A lot of builtin types have a reduce which 

608 # just raises a TypeError 

609 # we ignore those 

610 pass 

611 

612 # test for a reduce implementation, and redirect before 

613 # doing anything else if that is what reduce requests 

614 elif has_reduce_ex: 

615 try: 

616 # we're implementing protocol 2 

617 reduce_val = obj.__reduce_ex__(2) 

618 except TypeError: 

619 # A lot of builtin types have a reduce which 

620 # just raises a TypeError 

621 # we ignore those 

622 pass 

623 

624 if reduce_val and isinstance(reduce_val, str): 

625 try: 

626 varpath = iter(reduce_val.split('.')) 

627 # curmod will be transformed by the 

628 # loop into the value to pickle 

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

630 for modname in varpath: 

631 curmod = getattr(curmod, modname) 

632 # replace obj with value retrieved 

633 return self._flatten(curmod) 

634 except KeyError: 

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

636 pass 

637 

638 elif reduce_val: 

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

640 # pad out to len 5 

641 rv_as_list = list(reduce_val) 

642 insufficiency = 5 - len(rv_as_list) 

643 if insufficiency: 

644 rv_as_list += [None] * insufficiency 

645 

646 if getattr(rv_as_list[0], '__name__', '') == '__newobj__': 

647 rv_as_list[0] = tags.NEWOBJ 

648 

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

650 

651 # check that getstate/setstate is sane 

652 if not ( 

653 state 

654 and has_own_getstate 

655 and not hasattr(obj, '__setstate__') 

656 and not isinstance(obj, dict) 

657 ): 

658 # turn iterators to iterables for convenient serialization 

659 if rv_as_list[3]: 

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

661 

662 if rv_as_list[4]: 

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

664 

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

666 last_index = len(reduce_args) - 1 

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

668 last_index -= 1 

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

670 

671 return data 

672 

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

674 if self.unpicklable: 

675 data[tags.OBJECT] = class_name 

676 

677 if has_getnewargs_ex: 

678 data[tags.NEWARGSEX] = [ 

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

680 ] 

681 

682 if has_getnewargs and not has_getnewargs_ex: 

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

684 

685 if has_getinitargs: 

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

687 

688 if has_own_getstate: 

689 try: 

690 state = obj.__getstate__() 

691 except TypeError: 

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

693 # in Python3 

694 self._pickle_warning(obj) 

695 return None 

696 else: 

697 if state: 

698 return self._getstate(state, data) 

699 

700 if isinstance(obj, types.ModuleType): 

701 if self.unpicklable: 

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

703 else: 

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

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

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

707 return data 

708 

709 if util._is_dictionary_subclass(obj): 

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

711 return data 

712 

713 if util._is_sequence_subclass(obj): 

714 return self._flatten_sequence_obj(obj, data) 

715 

716 if util._is_iterator(obj): 

717 # force list in python 3 

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

719 return data 

720 

721 if has_dict: 

722 # Support objects that subclasses list and set 

723 if util._is_sequence_subclass(obj): 

724 return self._flatten_sequence_obj(obj, data) 

725 

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

727 getattr(obj, '_', None) 

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

729 

730 if has_slots: 

731 return self._flatten_newstyle_with_slots(obj, data) 

732 

733 # catchall return for data created above without a return 

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

735 if data: 

736 return data 

737 

738 self._pickle_warning(obj) 

739 return None 

740 

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

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

743 if self.unpicklable: 

744 if self._mkref(obj): 

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

746 # json representation. 

747 return self._flatten_obj_instance(obj) 

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

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

750 # when processing cyclical objects. 

751 return self._getref(obj) 

752 else: 

753 max_reached = self._max_reached() 

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

755 if in_cycle: 

756 # A circular becomes None. 

757 return None 

758 

759 self._mkref(obj) 

760 return self._flatten_obj_instance(obj) 

761 

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

763 return tags.JSON_KEY + encode( 

764 k, 

765 reset=False, 

766 keys=True, 

767 context=self, 

768 backend=self.backend, 

769 make_refs=self.make_refs, 

770 ) 

771 

772 def _flatten_non_string_key_value_pair( 

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

774 ) -> Dict[str, Any]: 

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

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

777 return data 

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

779 k = self._escape_key(k) 

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

781 return data 

782 

783 def _flatten_string_key_value_pair(self, k: str, v: Any, data: Dict[str, Any]): 

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

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

786 return data 

787 if self.keys: 

788 if not isinstance(k, str): 

789 return data 

790 elif k.startswith(tags.JSON_KEY): 

791 k = self._escape_key(k) 

792 else: 

793 if k is None: 

794 k = 'null' # for compatibility with common json encoders 

795 

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

797 pass 

798 elif not isinstance(k, str): 

799 try: 

800 k = repr(k) 

801 except Exception: 

802 k = str(k) 

803 

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

805 return data 

806 

807 def _flatten_dict_obj( 

808 self, 

809 obj: dict, 

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

811 exclude: Iterable[Any] = (), 

812 ) -> Dict[str, Any]: 

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

814 if data is None: 

815 data = obj.__class__() 

816 

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

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

819 if self.keys: 

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

821 flatten = self._flatten_string_key_value_pair 

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

823 flatten(k, v, data) 

824 

825 # Phase 2: serialize non-string keys. 

826 flatten = self._flatten_non_string_key_value_pair 

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

828 flatten(k, v, data) 

829 else: 

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

831 flatten = self._flatten_key_value_pair 

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

833 flatten(k, v, data) 

834 

835 # the collections.defaultdict protocol 

836 if hasattr(obj, 'default_factory') and callable(obj.default_factory): 

837 factory = obj.default_factory 

838 if util._is_type(factory): 

839 # Reference the class/type 

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

841 value: Dict[str, str] = _mktyperef(factory) 

842 else: 

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

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

845 if self._mkref(factory): 

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

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

848 # resulting instance is a suitable exemplar. 

849 value: Dict[str, Any] = self._flatten_obj_instance(handlers.CloneFactory(factory())) # type: ignore[no-redef] 

850 else: 

851 # We've seen this object before. 

852 # Break the cycle by emitting a reference. 

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

854 value: Dict[str, int] = self._getref(factory) # type: ignore[no-redef] 

855 data['default_factory'] = value 

856 

857 # Sub-classes of dict 

858 if hasattr(obj, '__dict__') and self.unpicklable and obj != obj.__dict__: 

859 if self._mkref(obj.__dict__): 

860 dict_data = {} 

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

862 data['__dict__'] = dict_data 

863 else: 

864 data['__dict__'] = self._getref(obj.__dict__) 

865 

866 return data 

867 

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

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

870 if self._mkref(obj): 

871 return ( 

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

873 ) 

874 else: 

875 return self._getref 

876 

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

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

879 if not self.unpicklable: 

880 return self._list_recurse 

881 return lambda obj: { 

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

883 self._flatten(v) for v in obj 

884 ] 

885 } 

886 

887 elif util._is_module_function(obj): 

888 return self._flatten_function 

889 

890 elif util._is_object(obj): 

891 return self._ref_obj_instance 

892 

893 elif util._is_type(obj): 

894 return _mktyperef 

895 

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

897 self._pickle_warning(obj) 

898 return None 

899 

900 def _flatten_sequence_obj( 

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

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

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

904 if hasattr(obj, '__dict__'): 

905 self._flatten_dict_obj(obj.__dict__, data) 

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

907 if self.unpicklable: 

908 data[tags.SEQ] = value 

909 else: 

910 return value 

911 return data