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

22 

23from . import tags 

24 

25# key 

26K = TypeVar("K") 

27# value 

28V = TypeVar("V") 

29# type 

30T = TypeVar("T") 

31 

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

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

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

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

36FUNCTION_TYPES: set[type] = { 

37 types.FunctionType, 

38 types.MethodType, 

39 types.LambdaType, 

40 types.BuiltinFunctionType, 

41 types.BuiltinMethodType, 

42} 

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

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

45NON_REDUCIBLE_TYPES: set[type] = ( 

46 { 

47 list, 

48 dict, 

49 set, 

50 tuple, 

51 object, 

52 bytes, 

53 } 

54 | PRIMITIVES 

55 | _NON_REDUCIBLE_FUNCTION_TYPES 

56) 

57NON_CLASS_TYPES: set[type] = { 

58 list, 

59 dict, 

60 set, 

61 tuple, 

62 bytes, 

63} | PRIMITIVES 

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

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

66 for name in types.__all__ 

67 if name.endswith("Type") 

68} 

69 

70 

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

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

73 

74 >>> _is_type(1) 

75 False 

76 

77 >>> _is_type(object) 

78 True 

79 

80 >>> class Klass: pass 

81 >>> _is_type(Klass) 

82 True 

83 """ 

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

85 return isinstance(obj, type) 

86 

87 

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

89 # false if attribute doesn't exist 

90 if not hasattr(obj, name): 

91 return False 

92 func = getattr(obj, name) 

93 

94 # builtin descriptors like __getnewargs__ 

95 if isinstance(func, types.BuiltinMethodType): 

96 return True 

97 

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

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

100 return False 

101 

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

103 # methods are essentially descriptors 

104 

105 # __class__ for old-style classes 

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

107 original = None 

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

109 for subtype in inspect.getmro(base_type): 

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

111 if original is not None: 

112 break 

113 

114 # name not found in the mro 

115 if original is None: 

116 return False 

117 

118 # static methods are always fine 

119 if isinstance(original, staticmethod): 

120 return True 

121 

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

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

124 return False 

125 bound_to = getattr(func, "__self__") 

126 

127 # class methods 

128 if isinstance(original, classmethod): 

129 return issubclass(base_type, bound_to) 

130 

131 # bound methods 

132 return isinstance(obj, type(bound_to)) 

133 

134 

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

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

137 

138 >>> _is_object(1) 

139 True 

140 

141 >>> _is_object(object()) 

142 True 

143 

144 >>> _is_object(lambda x: 1) 

145 False 

146 """ 

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

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

149 ) 

150 

151 

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

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

154 Used for serializing properties. 

155 """ 

156 return type(obj) in NON_CLASS_TYPES 

157 

158 

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

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

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

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

163 

164 >>> _is_primitive(3) 

165 True 

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

167 False 

168 """ 

169 return type(obj) in PRIMITIVES 

170 

171 

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

173 """Is the object an enum?""" 

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

175 

176 

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

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

179 a subclass and not the actual builtin dict. 

180 

181 >>> class Temp(dict): pass 

182 >>> _is_dictionary_subclass(Temp()) 

183 True 

184 """ 

185 # TODO: add UserDict 

186 return ( 

187 hasattr(obj, "__class__") 

188 and issubclass(obj.__class__, dict) 

189 and type(obj) is not dict 

190 ) 

191 

192 

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

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

195 

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

197 as list, set, tuple, etc.. 

198 

199 >>> class Temp(list): pass 

200 >>> _is_sequence_subclass(Temp()) 

201 True 

202 """ 

203 return ( 

204 hasattr(obj, "__class__") 

205 and issubclass(obj.__class__, SEQUENCES) 

206 and type(obj) not in SEQUENCES_SET 

207 ) 

208 

209 

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

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

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

213 

214 * :class:`~time.struct_time` 

215 """ 

216 return type(obj) is time.struct_time 

217 

218 

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

220 """Returns true if passed a function 

221 

222 >>> _is_function(lambda x: 1) 

223 True 

224 

225 >>> _is_function(locals) 

226 True 

227 

228 >>> def method(): pass 

229 >>> _is_function(method) 

230 True 

231 

232 >>> _is_function(1) 

233 False 

234 """ 

235 return type(obj) in FUNCTION_TYPES 

236 

237 

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

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

240 

241 >>> import os 

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

243 True 

244 

245 >>> _is_module_function(lambda: None) 

246 False 

247 

248 """ 

249 

250 return ( 

251 hasattr(obj, "__class__") 

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

253 and hasattr(obj, "__module__") 

254 and hasattr(obj, "__name__") 

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

256 ) or _is_cython_function(obj) 

257 

258 

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

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

261 

262 >>> import os 

263 >>> _is_picklable('os', os) 

264 True 

265 

266 >>> def foo(): pass 

267 >>> _is_picklable('foo', foo) 

268 True 

269 

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

271 False 

272 

273 """ 

274 if name in tags.RESERVED: 

275 return False 

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

277 

278 

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

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

281 

282 >>> _is_installed('sys') 

283 True 

284 >>> _is_installed('hopefullythisisnotarealmodule') 

285 False 

286 

287 """ 

288 try: 

289 __import__(module) 

290 return True 

291 except ImportError: 

292 return False 

293 

294 

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

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

297 

298 

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

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

301 

302 

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

304 try: 

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

306 except Exception: 

307 return False 

308 

309 

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

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

312 

313 

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

315 """ 

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

317 and should not have their __reduce__ methods used 

318 """ 

319 # defaultdicts may contain functions which we cannot serialise 

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

321 return True 

322 if ( 

323 type(obj) in NON_REDUCIBLE_TYPES 

324 or obj is object 

325 or _is_dictionary_subclass(obj) 

326 or isinstance(obj, types.ModuleType) 

327 or _is_reducible_sequence_subclass(obj) 

328 or _is_list_like(obj) 

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

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

331 ): 

332 return False 

333 return True 

334 

335 

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

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

338 return ( 

339 callable(obj) 

340 and hasattr(obj, "__repr__") 

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

342 ) 

343 

344 

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

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

347 try: 

348 setattr(obj, attr, value) 

349 return False 

350 except AttributeError: 

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

352 return True 

353 except TypeError: 

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

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

356 return True 

357 

358 

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

360 """ 

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

362 If obj.__dict__ is absent, return default 

363 """ 

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

365 

366 

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

368 """ 

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

370 If obj.__slots__ is absent, return default 

371 """ 

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

373 

374 

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

376 """ 

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

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

379 

380 Returns a tuple of booleans (has_reduce, has_reduce_ex) 

381 """ 

382 

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

384 return (False, False) 

385 

386 # in this case, reduce works and is desired 

387 # notwithstanding depending on default object 

388 # reduce 

389 if _is_noncomplex(obj): 

390 return (False, True) 

391 

392 has_reduce = False 

393 has_reduce_ex = False 

394 

395 REDUCE = "__reduce__" 

396 REDUCE_EX = "__reduce_ex__" 

397 

398 # For object instance 

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

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

401 

402 # turn to the MRO 

403 for base in type(obj).__mro__: 

404 if _is_reducible(base): 

405 has_reduce = has_reduce or in_dict(base, REDUCE) 

406 has_reduce_ex = has_reduce_ex or in_dict(base, REDUCE_EX) 

407 if has_reduce and has_reduce_ex: 

408 return (has_reduce, has_reduce_ex) 

409 

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

411 # getattred (rare, but includes some builtins) 

412 cls = type(obj) 

413 object_reduce = getattr(object, REDUCE) 

414 object_reduce_ex = getattr(object, REDUCE_EX) 

415 if not has_reduce: 

416 has_reduce_cls = getattr(cls, REDUCE, False) 

417 if has_reduce_cls is not object_reduce: 

418 has_reduce = has_reduce_cls 

419 

420 if not has_reduce_ex: 

421 has_reduce_ex_cls = getattr(cls, REDUCE_EX, False) 

422 if has_reduce_ex_cls is not object_reduce_ex: 

423 has_reduce_ex = has_reduce_ex_cls 

424 

425 return (has_reduce, has_reduce_ex) 

426 

427 

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

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

430 

431 Prefer the more modern naming. 

432 

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

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

435 name and unmap it when importing. 

436 

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

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

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

440 

441 See untranslate_module_name() for the reverse operation. 

442 """ 

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

444 return lookup.get(module, module) 

445 

446 

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

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

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

450 """ 

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

452 return lookup.get(module, module) 

453 

454 

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

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

457 

458 This reverses the translation applied by translate_module_name() to 

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

460 

461 """ 

462 return _0_9_6_compat_untranslate(module) 

463 

464 

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

466 """ 

467 >>> class Example(object): 

468 ... pass 

469 

470 >>> ex = Example() 

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

472 True 

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

474 True 

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

476 True 

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

478 True 

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

480 True 

481 >>> import argparse 

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

483 True 

484 

485 """ 

486 types_importable_name = _TYPES_IMPORTABLE_NAMES.get(cls) 

487 if types_importable_name is not None: 

488 return types_importable_name 

489 

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

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

492 module = translate_module_name(cls.__module__) 

493 if not module: 

494 if hasattr(cls, "__self__"): 

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

496 module = cls.__self__.__module__ 

497 else: 

498 module = cls.__self__.__class__.__module__ 

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

500 

501 

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

503 """ 

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

505 """ 

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

507 

508 

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

510 """ 

511 Decode payload - must be ascii text. 

512 """ 

513 try: 

514 return base64.b64decode(payload) 

515 except (TypeError, binascii.Error): 

516 return b"" 

517 

518 

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

520 """ 

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

522 """ 

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

524 

525 

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

527 """ 

528 Decode payload - must be ascii text. 

529 """ 

530 try: 

531 return base64.b85decode(payload) 

532 except (TypeError, ValueError): 

533 return b"" 

534 

535 

536def itemgetter( 

537 obj: Any, 

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

539) -> str: 

540 return str(getter(obj)) 

541 

542 

543def items( 

544 obj: dict[Any, Any], 

545 exclude: Iterable[Any] = (), 

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

547 """ 

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

549 Keep it for now. 

550 """ 

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

552 if k in exclude: 

553 continue 

554 yield k, v