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

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

176 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} 

53NON_REDUCIBLE_TYPES: set = ( 

54 { 

55 list, 

56 dict, 

57 set, 

58 tuple, 

59 object, 

60 bytes, 

61 } 

62 | PRIMITIVES 

63 | FUNCTION_TYPES 

64) 

65NON_CLASS_TYPES: set = { 

66 list, 

67 dict, 

68 set, 

69 tuple, 

70 bytes, 

71} | PRIMITIVES 

72 

73 

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

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

76 

77 >>> _is_type(1) 

78 False 

79 

80 >>> _is_type(object) 

81 True 

82 

83 >>> class Klass: pass 

84 >>> _is_type(Klass) 

85 True 

86 """ 

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

88 return isinstance(obj, type) 

89 

90 

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

92 # false if attribute doesn't exist 

93 if not hasattr(obj, name): 

94 return False 

95 func = getattr(obj, name) 

96 

97 # builtin descriptors like __getnewargs__ 

98 if isinstance(func, types.BuiltinMethodType): 

99 return True 

100 

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

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

103 return False 

104 

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

106 # methods are essentially descriptors 

107 

108 # __class__ for old-style classes 

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

110 original = None 

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

112 for subtype in inspect.getmro(base_type): 

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

114 if original is not None: 

115 break 

116 

117 # name not found in the mro 

118 if original is None: 

119 return False 

120 

121 # static methods are always fine 

122 if isinstance(original, staticmethod): 

123 return True 

124 

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

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

127 return False 

128 bound_to = getattr(func, '__self__') 

129 

130 # class methods 

131 if isinstance(original, classmethod): 

132 return issubclass(base_type, bound_to) 

133 

134 # bound methods 

135 return isinstance(obj, type(bound_to)) 

136 

137 

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

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

140 

141 >>> _is_object(1) 

142 True 

143 

144 >>> _is_object(object()) 

145 True 

146 

147 >>> _is_object(lambda x: 1) 

148 False 

149 """ 

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

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

152 ) 

153 

154 

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

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

157 Used for serializing properties. 

158 """ 

159 return type(obj) in NON_CLASS_TYPES 

160 

161 

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

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

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

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

166 

167 >>> _is_primitive(3) 

168 True 

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

170 False 

171 """ 

172 return type(obj) in PRIMITIVES 

173 

174 

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

176 """Is the object an enum?""" 

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

178 

179 

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

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

182 a subclass and not the actual builtin dict. 

183 

184 >>> class Temp(dict): pass 

185 >>> _is_dictionary_subclass(Temp()) 

186 True 

187 """ 

188 # TODO: add UserDict 

189 return ( 

190 hasattr(obj, '__class__') 

191 and issubclass(obj.__class__, dict) 

192 and type(obj) is not dict 

193 ) 

194 

195 

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

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

198 

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

200 as list, set, tuple, etc.. 

201 

202 >>> class Temp(list): pass 

203 >>> _is_sequence_subclass(Temp()) 

204 True 

205 """ 

206 return ( 

207 hasattr(obj, '__class__') 

208 and issubclass(obj.__class__, SEQUENCES) 

209 and type(obj) not in SEQUENCES_SET 

210 ) 

211 

212 

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

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

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

216 

217 * :class:`~time.struct_time` 

218 """ 

219 return type(obj) is time.struct_time 

220 

221 

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

223 """Returns true if passed a function 

224 

225 >>> _is_function(lambda x: 1) 

226 True 

227 

228 >>> _is_function(locals) 

229 True 

230 

231 >>> def method(): pass 

232 >>> _is_function(method) 

233 True 

234 

235 >>> _is_function(1) 

236 False 

237 """ 

238 return type(obj) in FUNCTION_TYPES 

239 

240 

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

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

243 

244 >>> import os 

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

246 True 

247 

248 >>> _is_module_function(lambda: None) 

249 False 

250 

251 """ 

252 

253 return ( 

254 hasattr(obj, '__class__') 

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

256 and hasattr(obj, '__module__') 

257 and hasattr(obj, '__name__') 

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

259 ) or _is_cython_function(obj) 

260 

261 

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

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

264 

265 >>> import os 

266 >>> _is_picklable('os', os) 

267 True 

268 

269 >>> def foo(): pass 

270 >>> _is_picklable('foo', foo) 

271 True 

272 

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

274 False 

275 

276 """ 

277 if name in tags.RESERVED: 

278 return False 

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

280 

281 

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

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

284 

285 >>> _is_installed('sys') 

286 True 

287 >>> _is_installed('hopefullythisisnotarealmodule') 

288 False 

289 

290 """ 

291 try: 

292 __import__(module) 

293 return True 

294 except ImportError: 

295 return False 

296 

297 

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

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

300 

301 

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

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

304 

305 

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

307 try: 

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

309 except Exception: 

310 return False 

311 

312 

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

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

315 

316 

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

318 """ 

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

320 and should not have their __reduce__ methods used 

321 """ 

322 # defaultdicts may contain functions which we cannot serialise 

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

324 return True 

325 if ( 

326 type(obj) in NON_REDUCIBLE_TYPES 

327 or obj is object 

328 or _is_dictionary_subclass(obj) 

329 or isinstance(obj, types.ModuleType) 

330 or _is_reducible_sequence_subclass(obj) 

331 or _is_list_like(obj) 

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

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

334 ): 

335 return False 

336 return True 

337 

338 

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

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

341 return ( 

342 callable(obj) 

343 and hasattr(obj, '__repr__') 

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

345 ) 

346 

347 

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

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

350 try: 

351 setattr(obj, attr, value) 

352 return False 

353 except AttributeError: 

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

355 return True 

356 except TypeError: 

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

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

359 return True 

360 

361 

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

363 """ 

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

365 If obj.__dict__ is absent, return default 

366 """ 

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

368 

369 

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

371 """ 

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

373 If obj.__slots__ is absent, return default 

374 """ 

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

376 

377 

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

379 """ 

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

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

382 

383 Returns a tuple of booleans (has_reduce, has_reduce_ex) 

384 """ 

385 

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

387 return (False, False) 

388 

389 # in this case, reduce works and is desired 

390 # notwithstanding depending on default object 

391 # reduce 

392 if _is_noncomplex(obj): 

393 return (False, True) 

394 

395 has_reduce = False 

396 has_reduce_ex = False 

397 

398 REDUCE = '__reduce__' 

399 REDUCE_EX = '__reduce_ex__' 

400 

401 # For object instance 

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

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

404 

405 # turn to the MRO 

406 for base in type(obj).__mro__: 

407 if _is_reducible(base): 

408 has_reduce = has_reduce or in_dict(base, REDUCE) 

409 has_reduce_ex = has_reduce_ex or in_dict(base, REDUCE_EX) 

410 if has_reduce and has_reduce_ex: 

411 return (has_reduce, has_reduce_ex) 

412 

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

414 # getattred (rare, but includes some builtins) 

415 cls = type(obj) 

416 object_reduce = getattr(object, REDUCE) 

417 object_reduce_ex = getattr(object, REDUCE_EX) 

418 if not has_reduce: 

419 has_reduce_cls = getattr(cls, REDUCE, False) 

420 if has_reduce_cls is not object_reduce: 

421 has_reduce = has_reduce_cls 

422 

423 if not has_reduce_ex: 

424 has_reduce_ex_cls = getattr(cls, REDUCE_EX, False) 

425 if has_reduce_ex_cls is not object_reduce_ex: 

426 has_reduce_ex = has_reduce_ex_cls 

427 

428 return (has_reduce, has_reduce_ex) 

429 

430 

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

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

433 

434 Prefer the more modern naming. 

435 

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

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

438 name and unmap it when importing. 

439 

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

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

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

443 

444 See untranslate_module_name() for the reverse operation. 

445 """ 

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

447 return lookup.get(module, module) 

448 

449 

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

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

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

453 """ 

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

455 return lookup.get(module, module) 

456 

457 

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

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

460 

461 This reverses the translation applied by translate_module_name() to 

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

463 

464 """ 

465 return _0_9_6_compat_untranslate(module) 

466 

467 

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

469 """ 

470 >>> class Example(object): 

471 ... pass 

472 

473 >>> ex = Example() 

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

475 True 

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

477 True 

478 >>> importable_name(None.__class__) == 'builtins.NoneType' 

479 True 

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

481 True 

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

483 True 

484 

485 """ 

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

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

488 module = translate_module_name(cls.__module__) 

489 if not module: 

490 if hasattr(cls, '__self__'): 

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

492 module = cls.__self__.__module__ 

493 else: 

494 module = cls.__self__.__class__.__module__ 

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

496 

497 

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

499 """ 

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

501 """ 

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

503 

504 

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

506 """ 

507 Decode payload - must be ascii text. 

508 """ 

509 try: 

510 return base64.b64decode(payload) 

511 except (TypeError, binascii.Error): 

512 return b'' 

513 

514 

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

516 """ 

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

518 """ 

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

520 

521 

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

523 """ 

524 Decode payload - must be ascii text. 

525 """ 

526 try: 

527 return base64.b85decode(payload) 

528 except (TypeError, ValueError): 

529 return b'' 

530 

531 

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

533 return str(getter(obj)) 

534 

535 

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

537 """ 

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

539 Keep it for now. 

540 """ 

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

542 if k in exclude: 

543 continue 

544 yield k, v