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

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

204 statements  

1# Copyright (C) 2008 John Paulett (john -at- paulett.org) 

2# Copyright (C) 2009-2018 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. 

7 

8"""Helper functions for pickling and unpickling. Most functions assist in 

9determining the type of an object. 

10""" 

11 

12import base64 

13import binascii 

14import collections 

15import inspect 

16import io 

17import operator 

18import sys 

19import time 

20import types 

21from collections.abc import Iterator as abc_iterator 

22from typing import ( 

23 Any, 

24 Callable, 

25 Dict, 

26 Iterable, 

27 Iterator, 

28 Optional, 

29 Type, 

30 TypeVar, 

31 Union, 

32) 

33 

34from . import tags 

35 

36# key 

37K = TypeVar("K") 

38# value 

39V = TypeVar("V") 

40# type 

41T = TypeVar("T") 

42 

43_ITERATOR_TYPE: type = type(iter("")) 

44SEQUENCES: tuple[type] = (list, set, tuple) # type: ignore[assignment] 

45SEQUENCES_SET: set[type] = {list, set, tuple} 

46PRIMITIVES: set[type] = {str, bool, int, float, type(None)} 

47FUNCTION_TYPES: set[type] = { 

48 types.FunctionType, 

49 types.MethodType, 

50 types.LambdaType, 

51 types.BuiltinFunctionType, 

52 types.BuiltinMethodType, 

53} 

54# Internal set for NON_REDUCIBLE_TYPES that excludes MethodType to allow method round-trip 

55_NON_REDUCIBLE_FUNCTION_TYPES: set[type] = FUNCTION_TYPES - {types.MethodType} 

56NON_REDUCIBLE_TYPES: set[type] = ( 

57 { 

58 list, 

59 dict, 

60 set, 

61 tuple, 

62 object, 

63 bytes, 

64 } 

65 | PRIMITIVES 

66 | _NON_REDUCIBLE_FUNCTION_TYPES 

67) 

68NON_CLASS_TYPES: set[type] = { 

69 list, 

70 dict, 

71 set, 

72 tuple, 

73 bytes, 

74} | PRIMITIVES 

75_TYPES_IMPORTABLE_NAMES: dict[Union[type, Callable[..., Any]], str] = { 

76 getattr(types, name): f"types.{name}" 

77 for name in types.__all__ 

78 if name.endswith("Type") 

79} 

80 

81 

82def _is_type(obj: Any) -> bool: 

83 """Returns True is obj is a reference to a type. 

84 

85 >>> _is_type(1) 

86 False 

87 

88 >>> _is_type(object) 

89 True 

90 

91 >>> class Klass: pass 

92 >>> _is_type(Klass) 

93 True 

94 """ 

95 # use "isinstance" and not "is" to allow for metaclasses 

96 return isinstance(obj, type) 

97 

98 

99def has_method(obj: Any, name: str) -> bool: 

100 # false if attribute doesn't exist 

101 if not hasattr(obj, name): 

102 return False 

103 func = getattr(obj, name) 

104 

105 # builtin descriptors like __getnewargs__ 

106 if isinstance(func, types.BuiltinMethodType): 

107 return True 

108 

109 # note that FunctionType has a different meaning in py2/py3 

110 if not isinstance(func, (types.MethodType, types.FunctionType)): 

111 return False 

112 

113 # need to go through __dict__'s since in py3 

114 # methods are essentially descriptors 

115 

116 # __class__ for old-style classes 

117 base_type = obj if _is_type(obj) else obj.__class__ 

118 original = None 

119 # there is no .mro() for old-style classes 

120 for subtype in inspect.getmro(base_type): 

121 original = vars(subtype).get(name) 

122 if original is not None: 

123 break 

124 

125 # name not found in the mro 

126 if original is None: 

127 return False 

128 

129 # static methods are always fine 

130 if isinstance(original, staticmethod): 

131 return True 

132 

133 # at this point, the method has to be an instancemthod or a classmethod 

134 if not hasattr(func, "__self__"): 

135 return False 

136 bound_to = getattr(func, "__self__") 

137 

138 # class methods 

139 if isinstance(original, classmethod): 

140 return issubclass(base_type, bound_to) 

141 

142 # bound methods 

143 return isinstance(obj, type(bound_to)) 

144 

145 

146def _is_object(obj: Any) -> bool: 

147 """Returns True is obj is a reference to an object instance. 

148 

149 >>> _is_object(1) 

150 True 

151 

152 >>> _is_object(object()) 

153 True 

154 

155 >>> _is_object(lambda x: 1) 

156 False 

157 """ 

158 return isinstance(obj, object) and not isinstance( 

159 obj, (type, types.FunctionType, types.BuiltinFunctionType) 

160 ) 

161 

162 

163def _is_not_class(obj: Any) -> bool: 

164 """Determines if the object is not a class or a class instance. 

165 Used for serializing properties. 

166 """ 

167 return type(obj) in NON_CLASS_TYPES 

168 

169 

170def _is_primitive(obj: Any) -> bool: 

171 """Helper method to see if the object is a basic data type. Unicode strings, 

172 integers, longs, floats, booleans, and None are considered primitive 

173 and will return True when passed into *_is_primitive()* 

174 

175 >>> _is_primitive(3) 

176 True 

177 >>> _is_primitive([4,4]) 

178 False 

179 """ 

180 return type(obj) in PRIMITIVES 

181 

182 

183def _is_enum(obj: Any) -> bool: 

184 """Is the object an enum?""" 

185 return "enum" in sys.modules and isinstance(obj, sys.modules["enum"].Enum) 

186 

187 

188def _is_dictionary_subclass(obj: Any) -> bool: 

189 """Returns True if *obj* is a subclass of the dict type. *obj* must be 

190 a subclass and not the actual builtin dict. 

191 

192 >>> class Temp(dict): pass 

193 >>> _is_dictionary_subclass(Temp()) 

194 True 

195 """ 

196 # TODO: add UserDict 

197 return ( 

198 hasattr(obj, "__class__") 

199 and issubclass(obj.__class__, dict) 

200 and type(obj) is not dict 

201 ) 

202 

203 

204def _is_sequence_subclass(obj: Any) -> bool: 

205 """Returns True if *obj* is a subclass of list, set or tuple. 

206 

207 *obj* must be a subclass and not the actual builtin, such 

208 as list, set, tuple, etc.. 

209 

210 >>> class Temp(list): pass 

211 >>> _is_sequence_subclass(Temp()) 

212 True 

213 """ 

214 return ( 

215 hasattr(obj, "__class__") 

216 and issubclass(obj.__class__, SEQUENCES) 

217 and type(obj) not in SEQUENCES_SET 

218 ) 

219 

220 

221def _is_noncomplex(obj: Any) -> bool: 

222 """Returns True if *obj* is a special (weird) class, that is more complex 

223 than primitive data types, but is not a full object. Including: 

224 

225 * :class:`~time.struct_time` 

226 """ 

227 return type(obj) is time.struct_time 

228 

229 

230def _is_function(obj: Any) -> bool: 

231 """Returns true if passed a function 

232 

233 >>> _is_function(lambda x: 1) 

234 True 

235 

236 >>> _is_function(locals) 

237 True 

238 

239 >>> def method(): pass 

240 >>> _is_function(method) 

241 True 

242 

243 >>> _is_function(1) 

244 False 

245 """ 

246 return type(obj) in FUNCTION_TYPES 

247 

248 

249def _is_module_function(obj: Any) -> bool: 

250 """Return True if `obj` is a module-global function 

251 

252 >>> import os 

253 >>> _is_module_function(os.path.exists) 

254 True 

255 

256 >>> _is_module_function(lambda: None) 

257 False 

258 

259 """ 

260 

261 return ( 

262 hasattr(obj, "__class__") 

263 and isinstance(obj, (types.FunctionType, types.BuiltinFunctionType)) 

264 and hasattr(obj, "__module__") 

265 and hasattr(obj, "__name__") 

266 and obj.__name__ != "<lambda>" 

267 ) or _is_cython_function(obj) 

268 

269 

270def _is_picklable(name: str, value: types.FunctionType) -> bool: 

271 """Return True if an object can be pickled 

272 

273 >>> import os 

274 >>> _is_picklable('os', os) 

275 True 

276 

277 >>> def foo(): pass 

278 >>> _is_picklable('foo', foo) 

279 True 

280 

281 >>> _is_picklable('foo', lambda: None) 

282 False 

283 

284 """ 

285 if name in tags.RESERVED: 

286 return False 

287 return _is_module_function(value) or not _is_function(value) 

288 

289 

290def _is_installed(module: str) -> bool: 

291 """Tests to see if ``module`` is available on the sys.path 

292 

293 >>> _is_installed('sys') 

294 True 

295 >>> _is_installed('hopefullythisisnotarealmodule') 

296 False 

297 

298 """ 

299 try: 

300 __import__(module) 

301 return True 

302 except ImportError: 

303 return False 

304 

305 

306def _is_list_like(obj: Any) -> bool: 

307 return hasattr(obj, "__getitem__") and hasattr(obj, "append") 

308 

309 

310def _is_iterator(obj: Any) -> bool: 

311 return isinstance(obj, abc_iterator) and not isinstance(obj, io.IOBase) 

312 

313 

314def _is_collections(obj: Any) -> bool: 

315 try: 

316 return type(obj).__module__ == "collections" 

317 except Exception: 

318 return False 

319 

320 

321def _is_reducible_sequence_subclass(obj: Any) -> bool: 

322 return hasattr(obj, "__class__") and issubclass(obj.__class__, SEQUENCES) 

323 

324 

325def _is_reducible(obj: Any) -> bool: 

326 """ 

327 Returns false if of a type which have special casing, 

328 and should not have their __reduce__ methods used 

329 """ 

330 # defaultdicts may contain functions which we cannot serialise 

331 if _is_collections(obj) and not isinstance(obj, collections.defaultdict): 

332 return True 

333 if ( 

334 type(obj) in NON_REDUCIBLE_TYPES 

335 or obj is object 

336 or _is_dictionary_subclass(obj) 

337 or isinstance(obj, types.ModuleType) 

338 or _is_reducible_sequence_subclass(obj) 

339 or _is_list_like(obj) 

340 or isinstance(getattr(obj, "__slots__", None), _ITERATOR_TYPE) 

341 or (_is_type(obj) and obj.__module__ == "datetime") 

342 ): 

343 return False 

344 return True 

345 

346 

347def _is_cython_function(obj: Any) -> bool: 

348 """Returns true if the object is a reference to a Cython function""" 

349 return ( 

350 callable(obj) 

351 and hasattr(obj, "__repr__") 

352 and repr(obj).startswith("<cyfunction ") 

353 ) 

354 

355 

356def _is_readonly(obj: Any, attr: str, value: Any) -> bool: 

357 # CPython 3.11+ has 0-cost try/except, please use up-to-date versions! 

358 try: 

359 setattr(obj, attr, value) 

360 return False 

361 except AttributeError: 

362 # this is okay, it means the attribute couldn't be set 

363 return True 

364 except TypeError: 

365 # this should only be happening when obj is a dict 

366 # as these errors happen when attr isn't a str 

367 return True 

368 

369 

370def in_dict(obj: Any, key: str, default: bool = False) -> bool: 

371 """ 

372 Returns true if key exists in obj.__dict__; false if not in. 

373 If obj.__dict__ is absent, return default 

374 """ 

375 return (key in obj.__dict__) if getattr(obj, "__dict__", None) else default 

376 

377 

378def in_slots(obj: Any, key: str, default: bool = False) -> bool: 

379 """ 

380 Returns true if key exists in obj.__slots__; false if not in. 

381 If obj.__slots__ is absent, return default 

382 """ 

383 return (key in obj.__slots__) if getattr(obj, "__slots__", None) else default 

384 

385 

386def has_reduce(obj: Any) -> tuple[bool, bool]: 

387 """ 

388 Tests if __reduce__ or __reduce_ex__ exists in the object dict or 

389 in the class dicts of every class in the MRO *except object*. 

390 

391 Returns a tuple of booleans (has_reduce, has_reduce_ex) 

392 """ 

393 

394 if not _is_reducible(obj) or _is_type(obj): 

395 return (False, False) 

396 

397 # in this case, reduce works and is desired 

398 # notwithstanding depending on default object 

399 # reduce 

400 if _is_noncomplex(obj): 

401 return (False, True) 

402 

403 has_reduce = False 

404 has_reduce_ex = False 

405 

406 REDUCE = "__reduce__" 

407 REDUCE_EX = "__reduce_ex__" 

408 

409 # For object instance 

410 has_reduce = in_dict(obj, REDUCE) or in_slots(obj, REDUCE) 

411 has_reduce_ex = in_dict(obj, REDUCE_EX) or in_slots(obj, REDUCE_EX) 

412 

413 # turn to the MRO 

414 for base in type(obj).__mro__: 

415 if _is_reducible(base): 

416 has_reduce = has_reduce or in_dict(base, REDUCE) 

417 has_reduce_ex = has_reduce_ex or in_dict(base, REDUCE_EX) 

418 if has_reduce and has_reduce_ex: 

419 return (has_reduce, has_reduce_ex) 

420 

421 # for things that don't have a proper dict but can be 

422 # getattred (rare, but includes some builtins) 

423 cls = type(obj) 

424 object_reduce = getattr(object, REDUCE) 

425 object_reduce_ex = getattr(object, REDUCE_EX) 

426 if not has_reduce: 

427 has_reduce_cls = getattr(cls, REDUCE, False) 

428 if has_reduce_cls is not object_reduce: 

429 has_reduce = has_reduce_cls 

430 

431 if not has_reduce_ex: 

432 has_reduce_ex_cls = getattr(cls, REDUCE_EX, False) 

433 if has_reduce_ex_cls is not object_reduce_ex: 

434 has_reduce_ex = has_reduce_ex_cls 

435 

436 return (has_reduce, has_reduce_ex) 

437 

438 

439def translate_module_name(module: str) -> str: 

440 """Rename builtin modules to a consistent module name. 

441 

442 Prefer the more modern naming. 

443 

444 This is used so that references to Python's `builtins` module can 

445 be loaded in both Python 2 and 3. We remap to the "__builtin__" 

446 name and unmap it when importing. 

447 

448 Map the Python2 `exceptions` module to `builtins` because 

449 `builtins` is a superset and contains everything that is 

450 available in `exceptions`, which makes the translation simpler. 

451 

452 See untranslate_module_name() for the reverse operation. 

453 """ 

454 lookup = dict(__builtin__="builtins", exceptions="builtins") 

455 return lookup.get(module, module) 

456 

457 

458def _0_9_6_compat_untranslate(module: str) -> str: 

459 """Provide compatibility for pickles created with jsonpickle 0.9.6 and 

460 earlier, remapping `exceptions` and `__builtin__` to `builtins`. 

461 """ 

462 lookup = dict(__builtin__="builtins", exceptions="builtins") 

463 return lookup.get(module, module) 

464 

465 

466def untranslate_module_name(module: str) -> str: 

467 """Rename module names mention in JSON to names that we can import 

468 

469 This reverses the translation applied by translate_module_name() to 

470 a module name available to the current version of Python. 

471 

472 """ 

473 return _0_9_6_compat_untranslate(module) 

474 

475 

476def importable_name(cls: Union[type, Callable[..., Any]]) -> str: 

477 """ 

478 >>> class Example(object): 

479 ... pass 

480 

481 >>> ex = Example() 

482 >>> importable_name(ex.__class__) == 'jsonpickle.util.Example' 

483 True 

484 >>> importable_name(type(25)) == 'builtins.int' 

485 True 

486 >>> importable_name(object().__str__.__class__) == 'types.MethodWrapperType' 

487 True 

488 >>> importable_name(False.__class__) == 'builtins.bool' 

489 True 

490 >>> importable_name(AttributeError) == 'builtins.AttributeError' 

491 True 

492 >>> import argparse 

493 >>> importable_name(type(argparse.ArgumentParser().add_argument)) == 'types.MethodType' 

494 True 

495 

496 """ 

497 types_importable_name = _TYPES_IMPORTABLE_NAMES.get(cls) 

498 if types_importable_name is not None: 

499 return types_importable_name 

500 

501 # Use the fully-qualified name if available (Python >= 3.3) 

502 name = getattr(cls, "__qualname__", cls.__name__) 

503 module = translate_module_name(cls.__module__) 

504 if not module: 

505 if hasattr(cls, "__self__"): 

506 if hasattr(cls.__self__, "__module__"): 

507 module = cls.__self__.__module__ 

508 else: 

509 module = cls.__self__.__class__.__module__ 

510 return f"{module}.{name}" 

511 

512 

513def b64encode(data: bytes) -> str: 

514 """ 

515 Encode binary data to ascii text in base64. Data must be bytes. 

516 """ 

517 return base64.b64encode(data).decode("ascii") 

518 

519 

520def b64decode(payload: str) -> bytes: 

521 """ 

522 Decode payload - must be ascii text. 

523 """ 

524 try: 

525 return base64.b64decode(payload) 

526 except (TypeError, binascii.Error): 

527 return b"" 

528 

529 

530def b85encode(data: bytes) -> str: 

531 """ 

532 Encode binary data to ascii text in base85. Data must be bytes. 

533 """ 

534 return base64.b85encode(data).decode("ascii") 

535 

536 

537def b85decode(payload: bytes) -> bytes: 

538 """ 

539 Decode payload - must be ascii text. 

540 """ 

541 try: 

542 return base64.b85decode(payload) 

543 except (TypeError, ValueError): 

544 return b"" 

545 

546 

547def itemgetter( 

548 obj: Any, 

549 getter: Callable[[Any], Any] = operator.itemgetter(0), 

550) -> str: 

551 return str(getter(obj)) 

552 

553 

554def items( 

555 obj: dict[Any, Any], 

556 exclude: Iterable[Any] = (), 

557) -> Iterator[tuple[Any, Any]]: 

558 """ 

559 This can't be easily replaced by dict.items() because this has the exclude parameter. 

560 Keep it for now. 

561 """ 

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

563 if k in exclude: 

564 continue 

565 yield k, v 

566 

567 

568def loadclass( 

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

570) -> Optional[Any]: 

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

572 

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

574 >>> cls.__name__ 

575 'datetime' 

576 

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

578 

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

580 0 

581 

582 """ 

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

584 if classes: 

585 try: 

586 return classes[module_and_name] 

587 except KeyError: 

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

589 try: 

590 return classes[module_and_name.rsplit(".", 1)[-1]] 

591 except KeyError: 

592 pass 

593 # Otherwise, load classes from globally-accessible imports 

594 names = module_and_name.split(".") 

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

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

597 # classes 

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

599 module = untranslate_module_name(".".join(names[:up_to])) 

600 try: 

601 __import__(module) 

602 obj = sys.modules[module] 

603 for class_name in names[up_to:]: 

604 obj = getattr(obj, class_name) 

605 return obj 

606 except (AttributeError, ImportError, ValueError): 

607 continue 

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

609 if module_and_name == "builtins.NoneType": 

610 return type(None) 

611 return None