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 Callable, Iterable, Iterator 

22from typing import Any, TypeVar 

23 

24from . import tags 

25 

26# key 

27K = TypeVar("K") 

28# value 

29V = TypeVar("V") 

30# type 

31T = TypeVar("T") 

32 

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

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

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

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

37FUNCTION_TYPES: set[type] = { 

38 types.FunctionType, 

39 types.MethodType, 

40 types.LambdaType, 

41 types.BuiltinFunctionType, 

42 types.BuiltinMethodType, 

43} 

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

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

46NON_REDUCIBLE_TYPES: set[type] = ( 

47 { 

48 list, 

49 dict, 

50 set, 

51 tuple, 

52 object, 

53 bytes, 

54 } 

55 | PRIMITIVES 

56 | _NON_REDUCIBLE_FUNCTION_TYPES 

57) 

58NON_CLASS_TYPES: set[type] = { 

59 list, 

60 dict, 

61 set, 

62 tuple, 

63 bytes, 

64} | PRIMITIVES 

65_TYPES_IMPORTABLE_NAMES: dict[type | Callable[..., Any], str] = { 

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

67 for name in types.__all__ 

68 if name.endswith("Type") 

69} 

70 

71 

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

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

74 

75 >>> _is_type(1) 

76 False 

77 

78 >>> _is_type(object) 

79 True 

80 

81 >>> class Klass: pass 

82 >>> _is_type(Klass) 

83 True 

84 """ 

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

86 return isinstance(obj, type) 

87 

88 

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

90 # false if attribute doesn't exist 

91 if not hasattr(obj, name): 

92 return False 

93 func = getattr(obj, name) 

94 

95 # builtin descriptors like __getnewargs__ 

96 if isinstance(func, types.BuiltinMethodType): 

97 return True 

98 

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

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

101 return False 

102 

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

104 # methods are essentially descriptors 

105 

106 # __class__ for old-style classes 

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

108 original = None 

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

110 for subtype in inspect.getmro(base_type): 

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

112 if original is not None: 

113 break 

114 

115 # name not found in the mro 

116 if original is None: 

117 return False 

118 

119 # static methods are always fine 

120 if isinstance(original, staticmethod): 

121 return True 

122 

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

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

125 return False 

126 bound_to = getattr(func, "__self__") 

127 

128 # class methods 

129 if isinstance(original, classmethod): 

130 return issubclass(base_type, bound_to) 

131 

132 # bound methods 

133 return isinstance(obj, type(bound_to)) 

134 

135 

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

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

138 

139 >>> _is_object(1) 

140 True 

141 

142 >>> _is_object(object()) 

143 True 

144 

145 >>> _is_object(lambda x: 1) 

146 False 

147 """ 

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

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

150 ) 

151 

152 

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

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

155 Used for serializing properties. 

156 """ 

157 return type(obj) in NON_CLASS_TYPES 

158 

159 

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

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

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

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

164 

165 >>> _is_primitive(3) 

166 True 

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

168 False 

169 """ 

170 return type(obj) in PRIMITIVES 

171 

172 

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

174 """Is the object an enum?""" 

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

176 

177 

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

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

180 a subclass and not the actual builtin dict. 

181 

182 >>> class Temp(dict): pass 

183 >>> _is_dictionary_subclass(Temp()) 

184 True 

185 """ 

186 # TODO: add UserDict 

187 return ( 

188 hasattr(obj, "__class__") 

189 and issubclass(obj.__class__, dict) 

190 and type(obj) is not dict 

191 ) 

192 

193 

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

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

196 

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

198 as list, set, tuple, etc.. 

199 

200 >>> class Temp(list): pass 

201 >>> _is_sequence_subclass(Temp()) 

202 True 

203 """ 

204 return ( 

205 hasattr(obj, "__class__") 

206 and issubclass(obj.__class__, SEQUENCES) 

207 and type(obj) not in SEQUENCES_SET 

208 ) 

209 

210 

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

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

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

214 

215 * :class:`~time.struct_time` 

216 """ 

217 return type(obj) is time.struct_time 

218 

219 

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

221 """Returns true if passed a function 

222 

223 >>> _is_function(lambda x: 1) 

224 True 

225 

226 >>> _is_function(locals) 

227 True 

228 

229 >>> def method(): pass 

230 >>> _is_function(method) 

231 True 

232 

233 >>> _is_function(1) 

234 False 

235 """ 

236 return type(obj) in FUNCTION_TYPES 

237 

238 

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

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

241 

242 >>> import os 

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

244 True 

245 

246 >>> _is_module_function(lambda: None) 

247 False 

248 

249 """ 

250 

251 return ( 

252 hasattr(obj, "__class__") 

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

254 and hasattr(obj, "__module__") 

255 and hasattr(obj, "__name__") 

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

257 ) or _is_cython_function(obj) 

258 

259 

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

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

262 

263 >>> import os 

264 >>> _is_picklable('os', os) 

265 True 

266 

267 >>> def foo(): pass 

268 >>> _is_picklable('foo', foo) 

269 True 

270 

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

272 False 

273 

274 """ 

275 if name in tags.RESERVED: 

276 return False 

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

278 

279 

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

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

282 

283 >>> _is_installed('sys') 

284 True 

285 >>> _is_installed('hopefullythisisnotarealmodule') 

286 False 

287 

288 """ 

289 try: 

290 __import__(module) 

291 return True 

292 except ImportError: 

293 return False 

294 

295 

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

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

298 

299 

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

301 return isinstance(obj, Iterator) and not isinstance(obj, io.IOBase) 

302 

303 

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

305 try: 

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

307 except Exception: 

308 return False 

309 

310 

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

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

313 

314 

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

316 """ 

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

318 and should not have their __reduce__ methods used 

319 """ 

320 # defaultdicts may contain functions which we cannot serialise 

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

322 return True 

323 if ( 

324 type(obj) in NON_REDUCIBLE_TYPES 

325 or obj is object 

326 or _is_dictionary_subclass(obj) 

327 or isinstance(obj, types.ModuleType) 

328 or _is_reducible_sequence_subclass(obj) 

329 or _is_list_like(obj) 

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

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

332 ): 

333 return False 

334 return True 

335 

336 

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

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

339 return ( 

340 callable(obj) 

341 and hasattr(obj, "__repr__") 

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

343 ) 

344 

345 

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

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

348 try: 

349 setattr(obj, attr, value) 

350 return False 

351 except AttributeError: 

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

353 return True 

354 except TypeError: 

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

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

357 return True 

358 

359 

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

361 """ 

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

363 If obj.__dict__ is absent, return default 

364 """ 

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

366 

367 

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

369 """ 

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

371 If obj.__slots__ is absent, return default 

372 """ 

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

374 

375 

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

377 """ 

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

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

380 

381 Returns a tuple of booleans (has_reduce, has_reduce_ex) 

382 """ 

383 

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

385 return (False, False) 

386 

387 # in this case, reduce works and is desired 

388 # notwithstanding depending on default object 

389 # reduce 

390 if _is_noncomplex(obj): 

391 return (False, True) 

392 

393 has_reduce = False 

394 has_reduce_ex = False 

395 

396 REDUCE = "__reduce__" 

397 REDUCE_EX = "__reduce_ex__" 

398 

399 # For object instance 

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

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

402 

403 # turn to the MRO 

404 for base in type(obj).__mro__: 

405 if _is_reducible(base): 

406 has_reduce = has_reduce or in_dict(base, REDUCE) 

407 has_reduce_ex = has_reduce_ex or in_dict(base, REDUCE_EX) 

408 if has_reduce and has_reduce_ex: 

409 return (has_reduce, has_reduce_ex) 

410 

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

412 # getattred (rare, but includes some builtins) 

413 cls = type(obj) 

414 object_reduce = getattr(object, REDUCE) 

415 object_reduce_ex = getattr(object, REDUCE_EX) 

416 if not has_reduce: 

417 has_reduce_cls = getattr(cls, REDUCE, False) 

418 if has_reduce_cls is not object_reduce: 

419 has_reduce = has_reduce_cls 

420 

421 if not has_reduce_ex: 

422 has_reduce_ex_cls = getattr(cls, REDUCE_EX, False) 

423 if has_reduce_ex_cls is not object_reduce_ex: 

424 has_reduce_ex = has_reduce_ex_cls 

425 

426 return (has_reduce, has_reduce_ex) 

427 

428 

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

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

431 

432 Prefer the more modern naming. 

433 

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

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

436 name and unmap it when importing. 

437 

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

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

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

441 

442 See untranslate_module_name() for the reverse operation. 

443 """ 

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

445 return lookup.get(module, module) 

446 

447 

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

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

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

451 """ 

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

453 return lookup.get(module, module) 

454 

455 

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

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

458 

459 This reverses the translation applied by translate_module_name() to 

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

461 

462 """ 

463 return _0_9_6_compat_untranslate(module) 

464 

465 

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

467 """ 

468 >>> class Example(object): 

469 ... pass 

470 

471 >>> ex = Example() 

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

473 True 

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

475 True 

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

477 True 

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

479 True 

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

481 True 

482 >>> import argparse 

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

484 True 

485 

486 """ 

487 types_importable_name = _TYPES_IMPORTABLE_NAMES.get(cls) 

488 if types_importable_name is not None: 

489 return types_importable_name 

490 

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

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

493 module = translate_module_name(cls.__module__) 

494 if not module: 

495 if hasattr(cls, "__self__"): 

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

497 module = cls.__self__.__module__ 

498 else: 

499 module = cls.__self__.__class__.__module__ 

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

501 

502 

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

504 """ 

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

506 """ 

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

508 

509 

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

511 """ 

512 Decode payload - must be ascii text. 

513 """ 

514 try: 

515 return base64.b64decode(payload) 

516 except (TypeError, binascii.Error): 

517 return b"" 

518 

519 

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

521 """ 

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

523 """ 

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

525 

526 

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

528 """ 

529 Decode payload - must be ascii text. 

530 """ 

531 try: 

532 return base64.b85decode(payload) 

533 except (TypeError, ValueError): 

534 return b"" 

535 

536 

537def itemgetter( 

538 obj: Any, 

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

540) -> str: 

541 return str(getter(obj)) 

542 

543 

544def items( 

545 obj: dict[Any, Any], 

546 exclude: Iterable[Any] = (), 

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

548 """ 

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

550 Keep it for now. 

551 """ 

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

553 if k in exclude: 

554 continue 

555 yield k, v 

556 

557 

558def loadclass( 

559 module_and_name: str, classes: dict[str, type] | None = None 

560) -> Any | None: 

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

562 

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

564 >>> cls.__name__ 

565 'datetime' 

566 

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

568 

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

570 0 

571 

572 """ 

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

574 if classes: 

575 try: 

576 return classes[module_and_name] 

577 except KeyError: 

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

579 try: 

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

581 except KeyError: 

582 pass 

583 # Otherwise, load classes from globally-accessible imports 

584 names = module_and_name.split(".") 

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

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

587 # classes 

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

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

590 try: 

591 __import__(module) 

592 obj = sys.modules[module] 

593 for class_name in names[up_to:]: 

594 obj = getattr(obj, class_name) 

595 return obj 

596 except (AttributeError, ImportError, ValueError): 

597 continue 

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

599 if module_and_name == "builtins.NoneType": 

600 return type(None) 

601 return None