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

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

181 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""" 

11import base64 

12import binascii 

13import collections 

14import inspect 

15import io 

16import operator 

17import sys 

18import time 

19import types 

20from collections.abc import Iterator as abc_iterator 

21from typing import ( 

22 Any, 

23 Callable, 

24 Iterable, 

25 Iterator, 

26 Mapping, 

27 Tuple, 

28 Type, 

29 TypeVar, 

30 Union, 

31) 

32 

33from . import tags 

34 

35# key 

36K = TypeVar("K") 

37# value 

38V = TypeVar("V") 

39# type 

40T = TypeVar("T") 

41 

42_ITERATOR_TYPE: type = type(iter('')) 

43SEQUENCES: tuple = (list, set, tuple) 

44SEQUENCES_SET: set = {list, set, tuple} 

45PRIMITIVES: set = {str, bool, int, float, type(None)} 

46FUNCTION_TYPES: set = { 

47 types.FunctionType, 

48 types.MethodType, 

49 types.LambdaType, 

50 types.BuiltinFunctionType, 

51 types.BuiltinMethodType, 

52} 

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

54_NON_REDUCIBLE_FUNCTION_TYPES: set = FUNCTION_TYPES - {types.MethodType} 

55NON_REDUCIBLE_TYPES: set = ( 

56 { 

57 list, 

58 dict, 

59 set, 

60 tuple, 

61 object, 

62 bytes, 

63 } 

64 | PRIMITIVES 

65 | _NON_REDUCIBLE_FUNCTION_TYPES 

66) 

67NON_CLASS_TYPES: set = { 

68 list, 

69 dict, 

70 set, 

71 tuple, 

72 bytes, 

73} | PRIMITIVES 

74_TYPES_IMPORTABLE_NAMES = { 

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

76 for name in types.__all__ 

77 if name.endswith("Type") 

78} 

79 

80 

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

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

83 

84 >>> _is_type(1) 

85 False 

86 

87 >>> _is_type(object) 

88 True 

89 

90 >>> class Klass: pass 

91 >>> _is_type(Klass) 

92 True 

93 """ 

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

95 return isinstance(obj, type) 

96 

97 

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

99 # false if attribute doesn't exist 

100 if not hasattr(obj, name): 

101 return False 

102 func = getattr(obj, name) 

103 

104 # builtin descriptors like __getnewargs__ 

105 if isinstance(func, types.BuiltinMethodType): 

106 return True 

107 

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

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

110 return False 

111 

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

113 # methods are essentially descriptors 

114 

115 # __class__ for old-style classes 

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

117 original = None 

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

119 for subtype in inspect.getmro(base_type): 

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

121 if original is not None: 

122 break 

123 

124 # name not found in the mro 

125 if original is None: 

126 return False 

127 

128 # static methods are always fine 

129 if isinstance(original, staticmethod): 

130 return True 

131 

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

133 if not hasattr(func, '__self__'): 

134 return False 

135 bound_to = getattr(func, '__self__') 

136 

137 # class methods 

138 if isinstance(original, classmethod): 

139 return issubclass(base_type, bound_to) 

140 

141 # bound methods 

142 return isinstance(obj, type(bound_to)) 

143 

144 

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

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

147 

148 >>> _is_object(1) 

149 True 

150 

151 >>> _is_object(object()) 

152 True 

153 

154 >>> _is_object(lambda x: 1) 

155 False 

156 """ 

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

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

159 ) 

160 

161 

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

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

164 Used for serializing properties. 

165 """ 

166 return type(obj) in NON_CLASS_TYPES 

167 

168 

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

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

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

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

173 

174 >>> _is_primitive(3) 

175 True 

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

177 False 

178 """ 

179 return type(obj) in PRIMITIVES 

180 

181 

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

183 """Is the object an enum?""" 

184 return 'enum' in sys.modules and isinstance(obj, sys.modules['enum'].Enum) 

185 

186 

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

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

189 a subclass and not the actual builtin dict. 

190 

191 >>> class Temp(dict): pass 

192 >>> _is_dictionary_subclass(Temp()) 

193 True 

194 """ 

195 # TODO: add UserDict 

196 return ( 

197 hasattr(obj, '__class__') 

198 and issubclass(obj.__class__, dict) 

199 and type(obj) is not dict 

200 ) 

201 

202 

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

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

205 

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

207 as list, set, tuple, etc.. 

208 

209 >>> class Temp(list): pass 

210 >>> _is_sequence_subclass(Temp()) 

211 True 

212 """ 

213 return ( 

214 hasattr(obj, '__class__') 

215 and issubclass(obj.__class__, SEQUENCES) 

216 and type(obj) not in SEQUENCES_SET 

217 ) 

218 

219 

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

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

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

223 

224 * :class:`~time.struct_time` 

225 """ 

226 return type(obj) is time.struct_time 

227 

228 

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

230 """Returns true if passed a function 

231 

232 >>> _is_function(lambda x: 1) 

233 True 

234 

235 >>> _is_function(locals) 

236 True 

237 

238 >>> def method(): pass 

239 >>> _is_function(method) 

240 True 

241 

242 >>> _is_function(1) 

243 False 

244 """ 

245 return type(obj) in FUNCTION_TYPES 

246 

247 

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

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

250 

251 >>> import os 

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

253 True 

254 

255 >>> _is_module_function(lambda: None) 

256 False 

257 

258 """ 

259 

260 return ( 

261 hasattr(obj, '__class__') 

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

263 and hasattr(obj, '__module__') 

264 and hasattr(obj, '__name__') 

265 and obj.__name__ != '<lambda>' 

266 ) or _is_cython_function(obj) 

267 

268 

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

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

271 

272 >>> import os 

273 >>> _is_picklable('os', os) 

274 True 

275 

276 >>> def foo(): pass 

277 >>> _is_picklable('foo', foo) 

278 True 

279 

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

281 False 

282 

283 """ 

284 if name in tags.RESERVED: 

285 return False 

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

287 

288 

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

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

291 

292 >>> _is_installed('sys') 

293 True 

294 >>> _is_installed('hopefullythisisnotarealmodule') 

295 False 

296 

297 """ 

298 try: 

299 __import__(module) 

300 return True 

301 except ImportError: 

302 return False 

303 

304 

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

306 return hasattr(obj, '__getitem__') and hasattr(obj, 'append') 

307 

308 

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

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

311 

312 

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

314 try: 

315 return type(obj).__module__ == 'collections' 

316 except Exception: 

317 return False 

318 

319 

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

321 return hasattr(obj, '__class__') and issubclass(obj.__class__, SEQUENCES) 

322 

323 

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

325 """ 

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

327 and should not have their __reduce__ methods used 

328 """ 

329 # defaultdicts may contain functions which we cannot serialise 

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

331 return True 

332 if ( 

333 type(obj) in NON_REDUCIBLE_TYPES 

334 or obj is object 

335 or _is_dictionary_subclass(obj) 

336 or isinstance(obj, types.ModuleType) 

337 or _is_reducible_sequence_subclass(obj) 

338 or _is_list_like(obj) 

339 or isinstance(getattr(obj, '__slots__', None), _ITERATOR_TYPE) 

340 or (_is_type(obj) and obj.__module__ == 'datetime') 

341 ): 

342 return False 

343 return True 

344 

345 

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

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

348 return ( 

349 callable(obj) 

350 and hasattr(obj, '__repr__') 

351 and repr(obj).startswith('<cyfunction ') 

352 ) 

353 

354 

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

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

357 try: 

358 setattr(obj, attr, value) 

359 return False 

360 except AttributeError: 

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

362 return True 

363 except TypeError: 

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

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

366 return True 

367 

368 

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

370 """ 

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

372 If obj.__dict__ is absent, return default 

373 """ 

374 return (key in obj.__dict__) if getattr(obj, '__dict__', None) else default 

375 

376 

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

378 """ 

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

380 If obj.__slots__ is absent, return default 

381 """ 

382 return (key in obj.__slots__) if getattr(obj, '__slots__', None) else default 

383 

384 

385def has_reduce(obj: Any) -> Tuple[bool, bool]: 

386 """ 

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

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

389 

390 Returns a tuple of booleans (has_reduce, has_reduce_ex) 

391 """ 

392 

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

394 return (False, False) 

395 

396 # in this case, reduce works and is desired 

397 # notwithstanding depending on default object 

398 # reduce 

399 if _is_noncomplex(obj): 

400 return (False, True) 

401 

402 has_reduce = False 

403 has_reduce_ex = False 

404 

405 REDUCE = '__reduce__' 

406 REDUCE_EX = '__reduce_ex__' 

407 

408 # For object instance 

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

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

411 

412 # turn to the MRO 

413 for base in type(obj).__mro__: 

414 if _is_reducible(base): 

415 has_reduce = has_reduce or in_dict(base, REDUCE) 

416 has_reduce_ex = has_reduce_ex or in_dict(base, REDUCE_EX) 

417 if has_reduce and has_reduce_ex: 

418 return (has_reduce, has_reduce_ex) 

419 

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

421 # getattred (rare, but includes some builtins) 

422 cls = type(obj) 

423 object_reduce = getattr(object, REDUCE) 

424 object_reduce_ex = getattr(object, REDUCE_EX) 

425 if not has_reduce: 

426 has_reduce_cls = getattr(cls, REDUCE, False) 

427 if has_reduce_cls is not object_reduce: 

428 has_reduce = has_reduce_cls 

429 

430 if not has_reduce_ex: 

431 has_reduce_ex_cls = getattr(cls, REDUCE_EX, False) 

432 if has_reduce_ex_cls is not object_reduce_ex: 

433 has_reduce_ex = has_reduce_ex_cls 

434 

435 return (has_reduce, has_reduce_ex) 

436 

437 

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

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

440 

441 Prefer the more modern naming. 

442 

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

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

445 name and unmap it when importing. 

446 

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

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

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

450 

451 See untranslate_module_name() for the reverse operation. 

452 """ 

453 lookup = dict(__builtin__='builtins', exceptions='builtins') 

454 return lookup.get(module, module) 

455 

456 

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

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

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

460 """ 

461 lookup = dict(__builtin__='builtins', exceptions='builtins') 

462 return lookup.get(module, module) 

463 

464 

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

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

467 

468 This reverses the translation applied by translate_module_name() to 

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

470 

471 """ 

472 return _0_9_6_compat_untranslate(module) 

473 

474 

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

476 """ 

477 >>> class Example(object): 

478 ... pass 

479 

480 >>> ex = Example() 

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

482 True 

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

484 True 

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

486 True 

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

488 True 

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

490 True 

491 >>> import argparse 

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

493 True 

494 

495 """ 

496 types_importable_name = _TYPES_IMPORTABLE_NAMES.get(cls) 

497 if types_importable_name is not None: 

498 return types_importable_name 

499 

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

501 name = getattr(cls, '__qualname__', cls.__name__) 

502 module = translate_module_name(cls.__module__) 

503 if not module: 

504 if hasattr(cls, '__self__'): 

505 if hasattr(cls.__self__, '__module__'): 

506 module = cls.__self__.__module__ 

507 else: 

508 module = cls.__self__.__class__.__module__ 

509 return f'{module}.{name}' 

510 

511 

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

513 """ 

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

515 """ 

516 return base64.b64encode(data).decode('ascii') 

517 

518 

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

520 """ 

521 Decode payload - must be ascii text. 

522 """ 

523 try: 

524 return base64.b64decode(payload) 

525 except (TypeError, binascii.Error): 

526 return b'' 

527 

528 

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

530 """ 

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

532 """ 

533 return base64.b85encode(data).decode('ascii') 

534 

535 

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

537 """ 

538 Decode payload - must be ascii text. 

539 """ 

540 try: 

541 return base64.b85decode(payload) 

542 except (TypeError, ValueError): 

543 return b'' 

544 

545 

546def itemgetter(obj: T, getter: Callable[[T], Any] = operator.itemgetter(0)) -> str: # type: ignore[assignment] 

547 return str(getter(obj)) 

548 

549 

550def items(obj: Mapping[K, V], exclude: Iterable[K] = ()) -> Iterator[Tuple[K, V]]: 

551 """ 

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

553 Keep it for now. 

554 """ 

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

556 if k in exclude: 

557 continue 

558 yield k, v